From 9f35794e90bd461fc337c5f2d22cd840dde8f9f8 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 9 Oct 2024 13:38:40 -0400 Subject: [PATCH 001/422] [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 002/422] [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 003/422] [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 004/422] [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 005/422] [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 006/422] [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 007/422] [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 008/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 34 +--- .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 34 files changed, 109 insertions(+), 325 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 0edec1d316..101f315a9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -88,11 +88,6 @@ export function collectHoistablePropertyLoads( ): ReadonlyMap { const registry = new PropertyPathRegistry(); - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - /** * Due to current limitations of mutable range inference, there are edge cases in * which we infer known-immutable values (e.g. props or hook params) to have a @@ -110,7 +105,7 @@ export function collectHoistablePropertyLoads( } } const nodes = collectNonNullsInBlocks(fn, { - temporaries: actuallyEvaluatedTemporaries, + temporaries, knownImmutableIdentifiers, hoistableFromOptionals, registry, @@ -576,30 +571,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index e771b386b3..a7fa0280d1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..fe98ee1bf7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 2a45eaca9e638007837911fdb0580e0384c79a93 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 21 Oct 2024 16:30:57 -0700 Subject: [PATCH 009/422] [compiler][ez] Clean up pragma parsing for tests + playground Move environment config parsing for `inlineJsxTransform`, `lowerContextAccess`, and some dev-only options out of snap (test fixture). These should now be available for playground via `@inlineJsxTransform` and `lowerContextAccess`. Other small change: Changed zod fields from `nullish()` -> `nullable().default(null)`. [`nullish`](https://zod.dev/?id=nullish) fields accept `null | undefined` and default to `undefined`. We don't distinguish between null and undefined for any of these options, so let's only accept null + default to null. This also makes EnvironmentConfig in the playground more accurate. Previously, some fields just didn't show up as `prettyFormat({field: undefined})` does not print `field`. --- .../src/HIR/Environment.ts | 92 +++++++++++++------ ...codegen-emit-imports-same-source.expect.md | 4 +- .../codegen-emit-imports-same-source.js | 2 +- ...en-instrument-forget-gating-test.expect.md | 4 +- .../codegen-instrument-forget-gating-test.js | 2 +- .../codegen-instrument-forget-test.expect.md | 4 +- .../codegen-instrument-forget-test.js | 2 +- .../compiler/inline-jsx-transform.expect.md | 4 +- .../fixtures/compiler/inline-jsx-transform.js | 2 +- compiler/packages/snap/src/compiler.ts | 70 +++----------- 10 files changed, 87 insertions(+), 99 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index b85d9425cb..8c615119d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -68,8 +68,8 @@ export const ExternalFunctionSchema = z.object({ export const InstrumentationSchema = z .object({ fn: ExternalFunctionSchema, - gating: ExternalFunctionSchema.nullish(), - globalGating: z.string().nullish(), + gating: ExternalFunctionSchema.nullable(), + globalGating: z.string().nullable(), }) .refine( opts => opts.gating != null || opts.globalGating != null, @@ -146,7 +146,7 @@ export type Hook = z.infer; */ const EnvironmentConfigSchema = z.object({ - customHooks: z.map(z.string(), HookSchema).optional().default(new Map()), + customHooks: z.map(z.string(), HookSchema).default(new Map()), /** * A function that, given the name of a module, can optionally return a description @@ -247,7 +247,7 @@ const EnvironmentConfigSchema = z.object({ * * The symbol configuration is set for backwards compatability with pre-React 19 transforms */ - inlineJsxTransform: ReactElementSymbolSchema.nullish(), + inlineJsxTransform: ReactElementSymbolSchema.nullable().default(null), /* * Enable validation of hooks to partially check that the component honors the rules of hooks. @@ -338,9 +338,9 @@ const EnvironmentConfigSchema = z.object({ * } * } */ - enableEmitFreeze: ExternalFunctionSchema.nullish(), + enableEmitFreeze: ExternalFunctionSchema.nullable().default(null), - enableEmitHookGuards: ExternalFunctionSchema.nullish(), + enableEmitHookGuards: ExternalFunctionSchema.nullable().default(null), /** * Enable instruction reordering. See InstructionReordering.ts for the details @@ -424,7 +424,7 @@ const EnvironmentConfigSchema = z.object({ * } * */ - enableEmitInstrumentForget: InstrumentationSchema.nullish(), + enableEmitInstrumentForget: InstrumentationSchema.nullable().default(null), // Enable validation of mutable ranges assertValidMutableRanges: z.boolean().default(false), @@ -463,8 +463,6 @@ const EnvironmentConfigSchema = z.object({ */ throwUnknownException__testonly: z.boolean().default(false), - enableSharedRuntime__testonly: z.boolean().default(false), - /** * Enables deps of a function epxression to be treated as conditional. This * makes sure we don't load a dep when it's a property (to check if it has @@ -502,7 +500,8 @@ const EnvironmentConfigSchema = z.object({ * computed one. This detects cases where rules of react violations may cause the * compiled code to behave differently than the original. */ - enableChangeDetectionForDebugging: ExternalFunctionSchema.nullish(), + enableChangeDetectionForDebugging: + ExternalFunctionSchema.nullable().default(null), /** * The react native re-animated library uses custom Babel transforms that @@ -542,7 +541,7 @@ const EnvironmentConfigSchema = z.object({ * * Here the variables `ref` and `myRef` will be typed as Refs. */ - enableTreatRefLikeIdentifiersAsRefs: z.boolean().nullable().default(false), + enableTreatRefLikeIdentifiersAsRefs: z.boolean().default(false), /* * If specified a value, the compiler lowers any calls to `useContext` to use @@ -564,11 +563,55 @@ const EnvironmentConfigSchema = z.object({ * const {foo, bar} = useCompiledContext(MyContext, (c) => [c.foo, c.bar]); * ``` */ - lowerContextAccess: ExternalFunctionSchema.nullish(), + lowerContextAccess: ExternalFunctionSchema.nullable().default(null), }); export type EnvironmentConfig = z.infer; +/** + * For test fixtures and playground only. + * + * Pragmas are straightforward to parse for boolean options (`:true` and + * `:false`). These are 'enabled' config values for non-boolean configs (i.e. + * what is used when parsing `:true`). + */ +const testConfigValues: PartialEnvironmentConfig = { + validateNoCapitalizedCalls: [], + enableChangeDetectionForDebugging: { + source: 'react-compiler-runtime', + importSpecifierName: '$structuralCheck', + }, + enableEmitFreeze: { + source: 'react-compiler-runtime', + importSpecifierName: 'makeReadOnly', + }, + enableEmitInstrumentForget: { + fn: { + source: 'react-compiler-runtime', + importSpecifierName: 'useRenderCounter', + }, + gating: { + source: 'react-compiler-runtime', + importSpecifierName: 'shouldInstrument', + }, + globalGating: '__DEV__', + }, + enableEmitHookGuards: { + source: 'react-compiler-runtime', + importSpecifierName: '$dispatcherGuard', + }, + inlineJsxTransform: { + elementSymbol: 'react.transitional.element', + }, + lowerContextAccess: { + source: 'react-compiler-runtime', + importSpecifierName: 'useContext_withSelector', + }, +}; + +/** + * For snap test fixtures and playground only. + */ export function parseConfigPragma(pragma: string): EnvironmentConfig { const maybeConfig: any = {}; // Get the defaults to programmatically check for boolean properties @@ -579,21 +622,12 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { continue; } const keyVal = token.slice(1); - let [key, val]: any = keyVal.split(':'); + let [key, val = undefined] = keyVal.split(':'); + const isSet = val === undefined || val === 'true'; - if (key === 'validateNoCapitalizedCalls') { - maybeConfig[key] = []; - continue; - } - - if ( - key === 'enableChangeDetectionForDebugging' && - (val === undefined || val === 'true') - ) { - maybeConfig[key] = { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }; + if (isSet && key in testConfigValues) { + maybeConfig[key] = + testConfigValues[key as keyof PartialEnvironmentConfig]; continue; } @@ -608,7 +642,6 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { props.push({type: 'name', name: elt}); } } - console.log([valSplit[0], props.map(x => x.name ?? '*').join('.')]); maybeConfig[key] = [[valSplit[0], props]]; } continue; @@ -619,11 +652,10 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { continue; } if (val === undefined || val === 'true') { - val = true; + maybeConfig[key] = true; } else { - val = false; + maybeConfig[key] = false; } - maybeConfig[key] = val; } const config = EnvironmentConfigSchema.safeParse(maybeConfig); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md index dd67bcfbff..9a59b36cc0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitFreeze @instrumentForget +// @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { return foo(props.x); @@ -18,7 +18,7 @@ import { shouldInstrument, makeReadOnly, } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget +import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { if (__DEV__ && shouldInstrument) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js index 4edff1c3fc..bd66353319 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js @@ -1,4 +1,4 @@ -// @enableEmitFreeze @instrumentForget +// @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { return foo(props.x); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md index 4aa29992eb..fc9247344d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @instrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode(annotation) @gating function Bar(props) { 'use forget'; @@ -25,7 +25,7 @@ function Foo(props) { ```javascript import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) @gating +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js index 85fbd97ee7..dffb8ce795 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js @@ -1,4 +1,4 @@ -// @instrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode(annotation) @gating function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md index ba8ed5056b..b5da853b6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @instrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { 'use forget'; @@ -24,7 +24,7 @@ function Foo(props) { ```javascript import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js index 8947503277..2aef527e6b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js @@ -1,4 +1,4 @@ -// @instrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md index 2078575e83..cf42c0ce75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableInlineJsxTransform +// @inlineJsxTransform function Parent({children, a: _a, b: _b, c: _c, ref}) { return
{children}
; @@ -60,7 +60,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c2 } from "react/compiler-runtime"; // @enableInlineJsxTransform +import { c as _c2 } from "react/compiler-runtime"; // @inlineJsxTransform function Parent(t0) { const $ = _c2(2); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js index 6fe9553dcd..e7487e2b36 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js @@ -1,4 +1,4 @@ -// @enableInlineJsxTransform +// @inlineJsxTransform function Parent({children, a: _a, b: _b, c: _c, ref}) { return
{children}
; diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index cd907575fb..a43ca48a6a 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -21,7 +21,6 @@ import type { } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; import type { - EnvironmentConfig, Macro, MacroMethod, parseConfigPragma as ParseConfigPragma, @@ -37,6 +36,11 @@ export function parseLanguage(source: string): 'flow' | 'typescript' { return source.indexOf('@flow') !== -1 ? 'flow' : 'typescript'; } +/** + * Parse react compiler plugin + environment options from test fixture. Note + * that although this primarily uses `Environment:parseConfigPragma`, it also + * has test fixture specific (i.e. not applicable to playground) parsing logic. + */ function makePluginOptions( firstLine: string, parseConfigPragmaFn: typeof ParseConfigPragma, @@ -44,15 +48,11 @@ function makePluginOptions( ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { let gating = null; - let enableEmitInstrumentForget = null; - let enableEmitFreeze = null; - let enableEmitHookGuards = null; let compilationMode: CompilationMode = 'all'; let panicThreshold: PanicThresholdOptions = 'all_errors'; let hookPattern: string | null = null; // TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false let validatePreserveExistingMemoizationGuarantees = false; - let enableChangeDetectionForDebugging = null; let customMacros: null | Array = null; let validateBlocklistedImports = null; let target = '19' as const; @@ -78,31 +78,6 @@ function makePluginOptions( importSpecifierName: 'isForgetEnabled_Fixtures', }; } - if (firstLine.includes('@instrumentForget')) { - enableEmitInstrumentForget = { - fn: { - source: 'react-compiler-runtime', - importSpecifierName: 'useRenderCounter', - }, - gating: { - source: 'react-compiler-runtime', - importSpecifierName: 'shouldInstrument', - }, - globalGating: '__DEV__', - }; - } - if (firstLine.includes('@enableEmitFreeze')) { - enableEmitFreeze = { - source: 'react-compiler-runtime', - importSpecifierName: 'makeReadOnly', - }; - } - if (firstLine.includes('@enableEmitHookGuards')) { - enableEmitHookGuards = { - source: 'react-compiler-runtime', - importSpecifierName: '$dispatcherGuard', - }; - } const targetMatch = /@target="([^"]+)"/.exec(firstLine); if (targetMatch) { @@ -132,16 +107,18 @@ function makePluginOptions( ignoreUseNoForget = true; } + /** + * Snap currently runs all fixtures without `validatePreserveExistingMemo` as + * most fixtures are interested in compilation output, not whether the + * compiler was able to preserve existing memo. + * + * TODO: flip the default. `useMemo` is rare in test fixtures -- fixtures that + * use useMemo should be explicit about whether this flag is enabled + */ if (firstLine.includes('@validatePreserveExistingMemoizationGuarantees')) { validatePreserveExistingMemoizationGuarantees = true; } - if (firstLine.includes('@enableChangeDetectionForDebugging')) { - enableChangeDetectionForDebugging = { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }; - } const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine); if ( hookPatternMatch && @@ -196,20 +173,6 @@ function makePluginOptions( .map(s => s.trim()) .filter(s => s.length > 0); } - - let lowerContextAccess = null; - if (firstLine.includes('@lowerContextAccess')) { - lowerContextAccess = { - source: 'react-compiler-runtime', - importSpecifierName: 'useContext_withSelector', - }; - } - - let inlineJsxTransform: EnvironmentConfig['inlineJsxTransform'] = null; - if (firstLine.includes('@enableInlineJsxTransform')) { - inlineJsxTransform = {elementSymbol: 'react.transitional.element'}; - } - let logs: Array<{filename: string | null; event: LoggerEvent}> = []; let logger: Logger | null = null; if (firstLine.includes('@logger')) { @@ -229,17 +192,10 @@ function makePluginOptions( ValueKindEnum, }), customMacros, - enableEmitFreeze, - enableEmitInstrumentForget, - enableEmitHookGuards, assertValidMutableRanges: true, - enableSharedRuntime__testonly: true, hookPattern, validatePreserveExistingMemoizationGuarantees, - enableChangeDetectionForDebugging, - lowerContextAccess, validateBlocklistedImports, - inlineJsxTransform, }, compilationMode, logger, From 515134d33c066404bf1fb97223021016a2eefb6d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 10:35:17 -0700 Subject: [PATCH 010/422] [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 011/422] [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 012/422] [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 013/422] [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 014/422] [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 015/422] [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 016/422] [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 017/422] [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 018/422] [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 019/422] [compiler][ez] Patch hoistability for ObjectMethods Extends #31066 to ObjectMethods (somehow missed this before). --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 +- .../infer-objectmethod-cond-access.expect.md | 82 +++++++++++++++++++ .../infer-objectmethod-cond-access.js | 26 ++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md new file mode 100644 index 0000000000..2c7bf6bbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(3); + const { a, shouldReadA } = t0; + let t1; + if ($[0] !== shouldReadA || $[1] !== a) { + t1 = ( + + ); + $[0] = shouldReadA; + $[1] = a; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ a: null, shouldReadA: true }], + sequentialRenders: [ + { a: null, shouldReadA: true }, + { a: null, shouldReadA: false }, + { a: { b: { c: 4 } }, shouldReadA: true }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]] +
{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}
+
{"objectMethod":{"method":{"kind":"Function","result":4}},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js new file mode 100644 index 0000000000..2c8488bb29 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js @@ -0,0 +1,26 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; From cbd12f5fcd98e4e92c3d06f9e5f3c89871ab6399 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 10:35:17 -0700 Subject: [PATCH 020/422] [compiler][ez] Patch hoistability for ObjectMethods Extends #31066 to ObjectMethods (somehow missed this before). - --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 +- .../infer-objectmethod-cond-access.expect.md | 82 +++++++++++++++++++ .../infer-objectmethod-cond-access.js | 26 ++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md new file mode 100644 index 0000000000..2c7bf6bbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(3); + const { a, shouldReadA } = t0; + let t1; + if ($[0] !== shouldReadA || $[1] !== a) { + t1 = ( + + ); + $[0] = shouldReadA; + $[1] = a; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ a: null, shouldReadA: true }], + sequentialRenders: [ + { a: null, shouldReadA: true }, + { a: null, shouldReadA: false }, + { a: { b: { c: 4 } }, shouldReadA: true }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]] +
{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}
+
{"objectMethod":{"method":{"kind":"Function","result":4}},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js new file mode 100644 index 0000000000..2c8488bb29 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js @@ -0,0 +1,26 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; From fad780511ab81cfed76b069b9a4d54ed57068b46 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 23 Oct 2024 11:10:03 -0700 Subject: [PATCH 021/422] [compiler] bugfix for hoistable deps for nested functions `PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions - --- .../src/HIR/CollectHoistablePropertyLoads.ts | 100 +++++++++++------- .../src/HIR/PropagateScopeDependenciesHIR.ts | 2 +- .../repro-invariant.expect.md | 61 +++++++++++ .../repro-invariant.tsx | 14 +++ 4 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d2e7220159..456425aeca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -1,5 +1,6 @@ import {CompilerError} from '../CompilerError'; import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; import { Set_equal, Set_filter, @@ -23,6 +24,8 @@ import { } from './HIR'; import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; +const DEBUG_PRINT = false; + /** * Helper function for `PropagateScopeDependencies`. Uses control flow graph * analysis to determine which `Identifier`s can be assumed to be non-null @@ -86,15 +89,8 @@ export function collectHoistablePropertyLoads( fn: HIRFunction, temporaries: ReadonlyMap, hoistableFromOptionals: ReadonlyMap, - nestedFnImmutableContext: ReadonlySet | null, ): ReadonlyMap { const registry = new PropertyPathRegistry(); - - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - /** * Due to current limitations of mutable range inference, there are edge cases in * which we infer known-immutable values (e.g. props or hook params) to have a @@ -111,14 +107,51 @@ export function collectHoistablePropertyLoads( } } } - const nodes = collectNonNullsInBlocks(fn, { - temporaries: actuallyEvaluatedTemporaries, + return collectHoistablePropertyLoadsImpl(fn, { + temporaries, knownImmutableIdentifiers, hoistableFromOptionals, registry, - nestedFnImmutableContext, + nestedFnImmutableContext: null, }); - propagateNonNull(fn, nodes, registry); +} + +type CollectHoistablePropertyLoadsContext = { + temporaries: ReadonlyMap; + knownImmutableIdentifiers: ReadonlySet; + hoistableFromOptionals: ReadonlyMap; + registry: PropertyPathRegistry; + /** + * (For nested / inner function declarations) + * Context variables (i.e. captured from an outer scope) that are immutable. + * Note that this technically could be merged into `knownImmutableIdentifiers`, + * but are currently kept separate for readability. + */ + nestedFnImmutableContext: ReadonlySet | null; +}; +function collectHoistablePropertyLoadsImpl( + fn: HIRFunction, + context: CollectHoistablePropertyLoadsContext, +): ReadonlyMap { + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); + const actuallyEvaluatedTemporaries = new Map( + [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), + ); + + const nodes = collectNonNullsInBlocks(fn, { + ...context, + temporaries: actuallyEvaluatedTemporaries, + }); + propagateNonNull(fn, nodes, context.registry); + + if (DEBUG_PRINT) { + console.log('(printing hoistable nodes in blocks)'); + for (const [blockId, node] of nodes) { + console.log( + `bb${blockId}: ${[...node.assumedNonNullObjects].map(n => printDependency(n.fullPath)).join(' ')}`, + ); + } + } return nodes; } @@ -243,7 +276,7 @@ class PropertyPathRegistry { function getMaybeNonNullInInstruction( instr: InstructionValue, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { let path = null; if (instr.kind === 'PropertyLoad') { @@ -262,7 +295,7 @@ function getMaybeNonNullInInstruction( function isImmutableAtInstr( identifier: Identifier, instr: InstructionId, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): boolean { if (context.nestedFnImmutableContext != null) { /** @@ -295,22 +328,9 @@ function isImmutableAtInstr( } } -type CollectNonNullsInBlocksContext = { - temporaries: ReadonlyMap; - knownImmutableIdentifiers: ReadonlySet; - hoistableFromOptionals: ReadonlyMap; - registry: PropertyPathRegistry; - /** - * (For nested / inner function declarations) - * Context variables (i.e. captured from an outer scope) that are immutable. - * Note that this technically could be merged into `knownImmutableIdentifiers`, - * but are currently kept separate for readability. - */ - nestedFnImmutableContext: ReadonlySet | null; -}; function collectNonNullsInBlocks( fn: HIRFunction, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { /** * Known non-null objects such as functional component props can be safely @@ -358,18 +378,22 @@ function collectNonNullsInBlocks( new Set(), ); const innerOptionals = collectOptionalChainSidemap(innerFn.func); - const innerHoistableMap = collectHoistablePropertyLoads( + const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, - innerTemporaries, - innerOptionals.hoistableObjects, - context.nestedFnImmutableContext ?? - new Set( - innerFn.func.context - .filter(place => - isImmutableAtInstr(place.identifier, instr.id, context), - ) - .map(place => place.identifier.id), - ), + { + ...context, + temporaries: innerTemporaries, // TODO: remove in later PR + hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR + nestedFnImmutableContext: + context.nestedFnImmutableContext ?? + new Set( + innerFn.func.context + .filter(place => + isImmutableAtInstr(place.identifier, instr.id, context), + ) + .map(place => place.identifier.id), + ), + }, ); const innerHoistables = assertNonNull( innerHoistableMap.get(innerFn.func.body.entry), diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 855ca9121d..0178aea6e4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -46,7 +46,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void { const hoistablePropertyLoads = keyByScopeId( fn, - collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null), + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects), ); const scopeDeps = collectDependencies( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md new file mode 100644 index 0000000000..73df2b615b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(5); + const { data } = t0; + let t1; + if ($[0] !== data.a.d) { + t1 = () => data.a.d; + $[0] = data.a.d; + $[1] = t1; + } else { + t1 = $[1]; + } + const t2 = data.a?.b.c; + let t3; + if ($[2] !== t1 || $[3] !== t2) { + t3 = ; + $[2] = t1; + $[3] = t2; + $[4] = t3; + } else { + t3 = $[4]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ data: { a: null } }], + sequentialRenders: [{ data: { a: { b: { c: 4 } } } }], +}; + +``` + +### Eval output +(kind: ok)
{"foo":{"kind":"Function"},"bar":4,"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx new file mode 100644 index 0000000000..05ed136d5f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx @@ -0,0 +1,14 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; From 1094c77f88e383a293a127044fdbac05b8727ba7 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 23 Oct 2024 11:10:04 -0700 Subject: [PATCH 022/422] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR - --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1869 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index b85d9425cb..4c57e792e3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,16 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..352552bf02 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 281d16137531363f368c43f8098cdb71cb2bd658 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:10:41 -0700 Subject: [PATCH 023/422] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for the next one. Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR - --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From a84a59d895312184a2b32b7903848f5825532985 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 024/422] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 4c57e792e3..31e42049fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,6 +230,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 02e60eff91..ed56ff0681 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 157e2de81a..bb99a5d90f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From a210420d89c9bdedca288917bc53b9e302a38321 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 025/422] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) - --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a179224a77..a772be62aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From 6c996df05bc2426bb1fa56a93212744fbe1e09ec Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 026/422] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s - --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From fd649b50acacffa2eaa1985712accab9277c3776 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 027/422] [compiler][be] Clean up nested function context in DCE Now that we rely on function context exclusively, let's clean up `HIRFunction.context` after DCE. This PR is in preparation of #31204, which would otherwise have unnecessary declarations (of context values that become entirely DCE'd) - --- .../src/Optimization/DeadCodeElimination.ts | 8 ++++ .../compiler/arrow-expr-directive.expect.md | 5 ++- .../compiler/capture-param-mutate.expect.md | 9 ++-- .../function-expr-directive.expect.md | 5 ++- .../compiler/merge-scopes-callback.expect.md | 5 ++- ...reactive-scope-with-early-return.expect.md | 42 ++++++++----------- ...react-hooks-based-on-import-name.expect.md | 5 ++- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 885ec2b3ab..0202d3ecf0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103..93eb2bd28a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c..9e4709616d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md index 3980434bde..8c4aa612e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md @@ -34,7 +34,7 @@ function Component() { t0 = function update() { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -51,6 +51,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md index edf748de5c..0ff9773f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md @@ -32,7 +32,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -61,6 +61,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index 0c1bf1cd70..506e4ca713 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -39,7 +39,7 @@ function Component() { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions function Component() { - const $ = _c(8); + const $ = _c(7); const items = useItems(); let t0; let t1; @@ -47,35 +47,25 @@ function Component() { if ($[0] !== items) { t2 = Symbol.for("react.early_return_sentinel"); bb0: { - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = (t4) => { - const [item] = t4; - return item.name != null; - }; - $[4] = t3; - } else { - t3 = $[4]; - } - t0 = items.filter(t3); + t0 = items.filter(_temp); const filteredItems = t0; if (filteredItems.length === 0) { - let t4; - if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t4 = ( + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (
); - $[5] = t4; + $[4] = t3; } else { - t4 = $[5]; + t3 = $[4]; } - t2 = t4; + t2 = t3; break bb0; } - t1 = filteredItems.map(_temp); + t1 = filteredItems.map(_temp2); } $[0] = items; $[1] = t1; @@ -90,19 +80,23 @@ function Component() { return t2; } let t3; - if ($[6] !== t1) { + if ($[5] !== t1) { t3 = <>{t1}; - $[6] = t1; - $[7] = t3; + $[5] = t1; + $[6] = t3; } else { - t3 = $[7]; + t3 = $[6]; } return t3; } -function _temp(t0) { +function _temp2(t0) { const [item_0] = t0; return ; } +function _temp(t0) { + const [item] = t0; + return item.name != null; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md index dc3081321e..496d61df9d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md @@ -38,7 +38,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -67,6 +67,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, From 3c0b16a3d2e1adac5b814d39014215d71101f9cc Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:15 -0700 Subject: [PATCH 028/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. - --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 36 files changed, 111 insertions(+), 332 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..3248775f7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,7 +231,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 1c6a90e74cfb5f2c1fb48e8b2c775b3b47088942 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 10:35:17 -0700 Subject: [PATCH 029/422] [compiler][ez] Patch hoistability for ObjectMethods Extends #31066 to ObjectMethods (somehow missed this before). ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 +- .../infer-objectmethod-cond-access.expect.md | 82 +++++++++++++++++++ .../infer-objectmethod-cond-access.js | 26 ++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md new file mode 100644 index 0000000000..2c7bf6bbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(3); + const { a, shouldReadA } = t0; + let t1; + if ($[0] !== shouldReadA || $[1] !== a) { + t1 = ( + + ); + $[0] = shouldReadA; + $[1] = a; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ a: null, shouldReadA: true }], + sequentialRenders: [ + { a: null, shouldReadA: true }, + { a: null, shouldReadA: false }, + { a: { b: { c: 4 } }, shouldReadA: true }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]] +
{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}
+
{"objectMethod":{"method":{"kind":"Function","result":4}},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js new file mode 100644 index 0000000000..2c8488bb29 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js @@ -0,0 +1,26 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; From ab8d3c5bdee07fcc7ce9f26c90bbd3916cfebe5c Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 23 Oct 2024 11:10:03 -0700 Subject: [PATCH 030/422] [compiler] bugfix for hoistable deps for nested functions `PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 100 +++++++++++------- .../src/HIR/PropagateScopeDependenciesHIR.ts | 2 +- .../repro-invariant.expect.md | 61 +++++++++++ .../repro-invariant.tsx | 14 +++ 4 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d2e7220159..456425aeca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -1,5 +1,6 @@ import {CompilerError} from '../CompilerError'; import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; import { Set_equal, Set_filter, @@ -23,6 +24,8 @@ import { } from './HIR'; import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; +const DEBUG_PRINT = false; + /** * Helper function for `PropagateScopeDependencies`. Uses control flow graph * analysis to determine which `Identifier`s can be assumed to be non-null @@ -86,15 +89,8 @@ export function collectHoistablePropertyLoads( fn: HIRFunction, temporaries: ReadonlyMap, hoistableFromOptionals: ReadonlyMap, - nestedFnImmutableContext: ReadonlySet | null, ): ReadonlyMap { const registry = new PropertyPathRegistry(); - - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - /** * Due to current limitations of mutable range inference, there are edge cases in * which we infer known-immutable values (e.g. props or hook params) to have a @@ -111,14 +107,51 @@ export function collectHoistablePropertyLoads( } } } - const nodes = collectNonNullsInBlocks(fn, { - temporaries: actuallyEvaluatedTemporaries, + return collectHoistablePropertyLoadsImpl(fn, { + temporaries, knownImmutableIdentifiers, hoistableFromOptionals, registry, - nestedFnImmutableContext, + nestedFnImmutableContext: null, }); - propagateNonNull(fn, nodes, registry); +} + +type CollectHoistablePropertyLoadsContext = { + temporaries: ReadonlyMap; + knownImmutableIdentifiers: ReadonlySet; + hoistableFromOptionals: ReadonlyMap; + registry: PropertyPathRegistry; + /** + * (For nested / inner function declarations) + * Context variables (i.e. captured from an outer scope) that are immutable. + * Note that this technically could be merged into `knownImmutableIdentifiers`, + * but are currently kept separate for readability. + */ + nestedFnImmutableContext: ReadonlySet | null; +}; +function collectHoistablePropertyLoadsImpl( + fn: HIRFunction, + context: CollectHoistablePropertyLoadsContext, +): ReadonlyMap { + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); + const actuallyEvaluatedTemporaries = new Map( + [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), + ); + + const nodes = collectNonNullsInBlocks(fn, { + ...context, + temporaries: actuallyEvaluatedTemporaries, + }); + propagateNonNull(fn, nodes, context.registry); + + if (DEBUG_PRINT) { + console.log('(printing hoistable nodes in blocks)'); + for (const [blockId, node] of nodes) { + console.log( + `bb${blockId}: ${[...node.assumedNonNullObjects].map(n => printDependency(n.fullPath)).join(' ')}`, + ); + } + } return nodes; } @@ -243,7 +276,7 @@ class PropertyPathRegistry { function getMaybeNonNullInInstruction( instr: InstructionValue, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { let path = null; if (instr.kind === 'PropertyLoad') { @@ -262,7 +295,7 @@ function getMaybeNonNullInInstruction( function isImmutableAtInstr( identifier: Identifier, instr: InstructionId, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): boolean { if (context.nestedFnImmutableContext != null) { /** @@ -295,22 +328,9 @@ function isImmutableAtInstr( } } -type CollectNonNullsInBlocksContext = { - temporaries: ReadonlyMap; - knownImmutableIdentifiers: ReadonlySet; - hoistableFromOptionals: ReadonlyMap; - registry: PropertyPathRegistry; - /** - * (For nested / inner function declarations) - * Context variables (i.e. captured from an outer scope) that are immutable. - * Note that this technically could be merged into `knownImmutableIdentifiers`, - * but are currently kept separate for readability. - */ - nestedFnImmutableContext: ReadonlySet | null; -}; function collectNonNullsInBlocks( fn: HIRFunction, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { /** * Known non-null objects such as functional component props can be safely @@ -358,18 +378,22 @@ function collectNonNullsInBlocks( new Set(), ); const innerOptionals = collectOptionalChainSidemap(innerFn.func); - const innerHoistableMap = collectHoistablePropertyLoads( + const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, - innerTemporaries, - innerOptionals.hoistableObjects, - context.nestedFnImmutableContext ?? - new Set( - innerFn.func.context - .filter(place => - isImmutableAtInstr(place.identifier, instr.id, context), - ) - .map(place => place.identifier.id), - ), + { + ...context, + temporaries: innerTemporaries, // TODO: remove in later PR + hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR + nestedFnImmutableContext: + context.nestedFnImmutableContext ?? + new Set( + innerFn.func.context + .filter(place => + isImmutableAtInstr(place.identifier, instr.id, context), + ) + .map(place => place.identifier.id), + ), + }, ); const innerHoistables = assertNonNull( innerHoistableMap.get(innerFn.func.body.entry), diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 855ca9121d..0178aea6e4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -46,7 +46,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void { const hoistablePropertyLoads = keyByScopeId( fn, - collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null), + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects), ); const scopeDeps = collectDependencies( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md new file mode 100644 index 0000000000..73df2b615b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(5); + const { data } = t0; + let t1; + if ($[0] !== data.a.d) { + t1 = () => data.a.d; + $[0] = data.a.d; + $[1] = t1; + } else { + t1 = $[1]; + } + const t2 = data.a?.b.c; + let t3; + if ($[2] !== t1 || $[3] !== t2) { + t3 = ; + $[2] = t1; + $[3] = t2; + $[4] = t3; + } else { + t3 = $[4]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ data: { a: null } }], + sequentialRenders: [{ data: { a: { b: { c: 4 } } } }], +}; + +``` + +### Eval output +(kind: ok)
{"foo":{"kind":"Function"},"bar":4,"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx new file mode 100644 index 0000000000..05ed136d5f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx @@ -0,0 +1,14 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; From fba4c34063ede95354448bf59093c557fc441d8d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 23 Oct 2024 11:10:04 -0700 Subject: [PATCH 031/422] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR ' --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1869 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index b85d9425cb..4c57e792e3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,16 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..352552bf02 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 01cf67c06c5c2585549615ee55a16bcd429295b4 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:10:41 -0700 Subject: [PATCH 032/422] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for the next one. Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From c5b7421f24600cc8fc51dd654bd89ee6b068a074 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 033/422] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 4c57e792e3..31e42049fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,6 +230,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 02e60eff91..ed56ff0681 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 157e2de81a..bb99a5d90f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From 1ff42cd89d5fc56644370aa7c549a05c310af309 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 034/422] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a179224a77..a772be62aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From a1f5b655e4b8fa9ebb43ee307d164f6424e7e1cc Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 035/422] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From ad0560f503e09293265c1f08c07064133c0841a3 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 036/422] [compiler][be] Clean up nested function context in DCE Now that we rely on function context exclusively, let's clean up `HIRFunction.context` after DCE. This PR is in preparation of #31204, which would otherwise have unnecessary declarations (of context values that become entirely DCE'd) ' --- .../src/Optimization/DeadCodeElimination.ts | 8 ++++ .../compiler/arrow-expr-directive.expect.md | 5 ++- .../compiler/capture-param-mutate.expect.md | 9 ++-- .../function-expr-directive.expect.md | 5 ++- .../compiler/merge-scopes-callback.expect.md | 5 ++- ...reactive-scope-with-early-return.expect.md | 42 ++++++++----------- ...react-hooks-based-on-import-name.expect.md | 5 ++- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 885ec2b3ab..0202d3ecf0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103..93eb2bd28a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c..9e4709616d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md index 3980434bde..8c4aa612e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md @@ -34,7 +34,7 @@ function Component() { t0 = function update() { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -51,6 +51,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md index edf748de5c..0ff9773f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md @@ -32,7 +32,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -61,6 +61,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index 0c1bf1cd70..506e4ca713 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -39,7 +39,7 @@ function Component() { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions function Component() { - const $ = _c(8); + const $ = _c(7); const items = useItems(); let t0; let t1; @@ -47,35 +47,25 @@ function Component() { if ($[0] !== items) { t2 = Symbol.for("react.early_return_sentinel"); bb0: { - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = (t4) => { - const [item] = t4; - return item.name != null; - }; - $[4] = t3; - } else { - t3 = $[4]; - } - t0 = items.filter(t3); + t0 = items.filter(_temp); const filteredItems = t0; if (filteredItems.length === 0) { - let t4; - if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t4 = ( + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (
); - $[5] = t4; + $[4] = t3; } else { - t4 = $[5]; + t3 = $[4]; } - t2 = t4; + t2 = t3; break bb0; } - t1 = filteredItems.map(_temp); + t1 = filteredItems.map(_temp2); } $[0] = items; $[1] = t1; @@ -90,19 +80,23 @@ function Component() { return t2; } let t3; - if ($[6] !== t1) { + if ($[5] !== t1) { t3 = <>{t1}; - $[6] = t1; - $[7] = t3; + $[5] = t1; + $[6] = t3; } else { - t3 = $[7]; + t3 = $[6]; } return t3; } -function _temp(t0) { +function _temp2(t0) { const [item_0] = t0; return ; } +function _temp(t0) { + const [item] = t0; + return item.name != null; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md index dc3081321e..496d61df9d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md @@ -38,7 +38,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -67,6 +67,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, From 8e5c4c1bd61a8e0fdfae3e38c9d30f3f6e11dcce Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:15 -0700 Subject: [PATCH 037/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 36 files changed, 111 insertions(+), 332 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..3248775f7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,7 +231,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From a5b8191460d85318d36f111c76792939cd241a61 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 10:14:18 -0700 Subject: [PATCH 038/422] [compiler] Add fixture for objectexpr computed key bug We're currently bailing out on complex computed-key syntax as we assumed that caused bugs (due to inferring computed key rvalues to have `freeze` effects). This fixture shows that this bailout is unrelated to the underlying bug --- ...nstruction-hoisted-sequence-expr.expect.md | 109 ++++++++++++++++++ ...fter-construction-hoisted-sequence-expr.js | 31 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 141 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md new file mode 100644 index 0000000000..dcca6cddd8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md @@ -0,0 +1,109 @@ + +## Input + +```javascript +import {identity, mutate} from 'shared-runtime'; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const key = {}; + const tmp = (mutate(key), key); + const context = { + // Here, `tmp` is frozen (as it's inferred to be a primitive/string) + [tmp]: identity([props.value]), + }; + mutate(key); + return [context, key]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 42}], + sequentialRenders: [{value: 42}, {value: 42}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { identity, mutate } from "shared-runtime"; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const $ = _c(8); + let t0; + let key; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + key = {}; + t0 = (mutate(key), key); + $[0] = t0; + $[1] = key; + } else { + t0 = $[0]; + key = $[1]; + } + const tmp = t0; + let t1; + if ($[2] !== props.value) { + t1 = identity([props.value]); + $[2] = props.value; + $[3] = t1; + } else { + t1 = $[3]; + } + let t2; + if ($[4] !== t1) { + t2 = { [tmp]: t1 }; + $[4] = t1; + $[5] = t2; + } else { + t2 = $[5]; + } + const context = t2; + + mutate(key); + let t3; + if ($[6] !== context) { + t3 = [context, key]; + $[6] = context; + $[7] = t3; + } else { + t3 = $[7]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], + sequentialRenders: [{ value: 42 }, { value: 42 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js new file mode 100644 index 0000000000..94befbdd17 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js @@ -0,0 +1,31 @@ +import {identity, mutate} from 'shared-runtime'; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const key = {}; + const tmp = (mutate(key), key); + const context = { + // Here, `tmp` is frozen (as it's inferred to be a primitive/string) + [tmp]: identity([props.value]), + }; + mutate(key); + return [context, key]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 42}], + sequentialRenders: [{value: 42}, {value: 42}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..76914f1dd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -478,6 +478,7 @@ const skipFilter = new Set([ // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', + 'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr', 'bug-invalid-hoisting-functionexpr', 'bug-try-catch-maybe-null-dependency', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', From 673a25ba8ce7957eed8727e5f7a0dd7034034108 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 17:17:31 -0700 Subject: [PATCH 039/422] [compiler][ez] Fixture repro for function hoisting bug Repro for bug reported by @alexmckenley --- .../bug-functiondecl-hoisting.expect.md | 81 +++++++++++++++++++ .../compiler/bug-functiondecl-hoisting.tsx | 22 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 104 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md new file mode 100644 index 0000000000..bd567de90d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md @@ -0,0 +1,81 @@ + +## Input + +```javascript +import { Stringify } from "shared-runtime"; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo(t0) { + const $ = _c(6); + const { value } = t0; + let bar; + let result; + if ($[0] !== value) { + result = bar(); + bar = function bar() { + return { value }; + }; + $[0] = value; + $[1] = bar; + $[2] = result; + } else { + bar = $[1]; + result = $[2]; + } + + const t1 = bar; + let t2; + if ($[3] !== result || $[4] !== t1) { + t2 = ; + $[3] = result; + $[4] = t1; + $[5] = t2; + } else { + t2 = $[5]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 2 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx new file mode 100644 index 0000000000..3500a0629d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx @@ -0,0 +1,22 @@ +import { Stringify } from "shared-runtime"; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..0c7ab28ac0 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -479,6 +479,7 @@ const skipFilter = new Set([ 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', 'bug-invalid-hoisting-functionexpr', + 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', From 2b8c1218b7b905c0eb243ab767dc7e4ceda345ac Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 17:06:11 -0700 Subject: [PATCH 040/422] [compiler][ez] tsconfig: treat all snap fixtures as modules Qol improvement. Currently, typescript lints treat test fixtures without an export as a 'global script' (see [docs](https://www.typescriptlang.org/docs/handbook/2/modules.html#how-javascript-modules-are-defined)). This gives confusing lints for duplicate declarations (in the global scope) --- .../src/__tests__/fixtures/tsconfig.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/tsconfig.json b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/tsconfig.json index 07d6d2baae..7c51d213ed 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/tsconfig.json +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/tsconfig.json @@ -19,7 +19,9 @@ }, "verbatimModuleSyntax": true, "module": "ESNext", - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "moduleDetection": "force" + }, "include": [ "./compiler/**/*.js", From 18b760e99d3ac8d0efb40f4226a7b773dba4c2bb Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:25:24 -0700 Subject: [PATCH 041/422] [compiler][ez] Fixture repro for function hoisting bug Repro for bug reported by @alexmckenley --- .../bug-functiondecl-hoisting.expect.md | 81 +++++++++++++++++++ .../compiler/bug-functiondecl-hoisting.tsx | 22 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 104 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md new file mode 100644 index 0000000000..2b0031b117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md @@ -0,0 +1,81 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo(t0) { + const $ = _c(6); + const { value } = t0; + let bar; + let result; + if ($[0] !== value) { + result = bar(); + bar = function bar() { + return { value }; + }; + $[0] = value; + $[1] = bar; + $[2] = result; + } else { + bar = $[1]; + result = $[2]; + } + + const t1 = bar; + let t2; + if ($[3] !== result || $[4] !== t1) { + t2 = ; + $[3] = result; + $[4] = t1; + $[5] = t2; + } else { + t2 = $[5]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 2 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx new file mode 100644 index 0000000000..c454101282 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx @@ -0,0 +1,22 @@ +import {Stringify} from 'shared-runtime'; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..0c7ab28ac0 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -479,6 +479,7 @@ const skipFilter = new Set([ 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', 'bug-invalid-hoisting-functionexpr', + 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', From d0c31e9dd2c43b152b67715beba5181e4d620bf0 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 042/422] [compiler][ez] Patch hoistability for ObjectMethods Extends #31066 to ObjectMethods (somehow missed this before). ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 +- .../infer-objectmethod-cond-access.expect.md | 82 +++++++++++++++++++ .../infer-objectmethod-cond-access.js | 26 ++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md new file mode 100644 index 0000000000..2c7bf6bbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(3); + const { a, shouldReadA } = t0; + let t1; + if ($[0] !== shouldReadA || $[1] !== a) { + t1 = ( + + ); + $[0] = shouldReadA; + $[1] = a; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ a: null, shouldReadA: true }], + sequentialRenders: [ + { a: null, shouldReadA: true }, + { a: null, shouldReadA: false }, + { a: { b: { c: 4 } }, shouldReadA: true }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]] +
{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}
+
{"objectMethod":{"method":{"kind":"Function","result":4}},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js new file mode 100644 index 0000000000..2c8488bb29 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js @@ -0,0 +1,26 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; From ce0115c95a30f3cf8243491aecde00db2e442ee9 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 043/422] [compiler] bugfix for hoistable deps for nested functions `PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 100 +++++++++++------- .../src/HIR/PropagateScopeDependenciesHIR.ts | 2 +- .../repro-invariant.expect.md | 61 +++++++++++ .../repro-invariant.tsx | 14 +++ 4 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d2e7220159..456425aeca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -1,5 +1,6 @@ import {CompilerError} from '../CompilerError'; import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; import { Set_equal, Set_filter, @@ -23,6 +24,8 @@ import { } from './HIR'; import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; +const DEBUG_PRINT = false; + /** * Helper function for `PropagateScopeDependencies`. Uses control flow graph * analysis to determine which `Identifier`s can be assumed to be non-null @@ -86,15 +89,8 @@ export function collectHoistablePropertyLoads( fn: HIRFunction, temporaries: ReadonlyMap, hoistableFromOptionals: ReadonlyMap, - nestedFnImmutableContext: ReadonlySet | null, ): ReadonlyMap { const registry = new PropertyPathRegistry(); - - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - /** * Due to current limitations of mutable range inference, there are edge cases in * which we infer known-immutable values (e.g. props or hook params) to have a @@ -111,14 +107,51 @@ export function collectHoistablePropertyLoads( } } } - const nodes = collectNonNullsInBlocks(fn, { - temporaries: actuallyEvaluatedTemporaries, + return collectHoistablePropertyLoadsImpl(fn, { + temporaries, knownImmutableIdentifiers, hoistableFromOptionals, registry, - nestedFnImmutableContext, + nestedFnImmutableContext: null, }); - propagateNonNull(fn, nodes, registry); +} + +type CollectHoistablePropertyLoadsContext = { + temporaries: ReadonlyMap; + knownImmutableIdentifiers: ReadonlySet; + hoistableFromOptionals: ReadonlyMap; + registry: PropertyPathRegistry; + /** + * (For nested / inner function declarations) + * Context variables (i.e. captured from an outer scope) that are immutable. + * Note that this technically could be merged into `knownImmutableIdentifiers`, + * but are currently kept separate for readability. + */ + nestedFnImmutableContext: ReadonlySet | null; +}; +function collectHoistablePropertyLoadsImpl( + fn: HIRFunction, + context: CollectHoistablePropertyLoadsContext, +): ReadonlyMap { + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); + const actuallyEvaluatedTemporaries = new Map( + [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), + ); + + const nodes = collectNonNullsInBlocks(fn, { + ...context, + temporaries: actuallyEvaluatedTemporaries, + }); + propagateNonNull(fn, nodes, context.registry); + + if (DEBUG_PRINT) { + console.log('(printing hoistable nodes in blocks)'); + for (const [blockId, node] of nodes) { + console.log( + `bb${blockId}: ${[...node.assumedNonNullObjects].map(n => printDependency(n.fullPath)).join(' ')}`, + ); + } + } return nodes; } @@ -243,7 +276,7 @@ class PropertyPathRegistry { function getMaybeNonNullInInstruction( instr: InstructionValue, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { let path = null; if (instr.kind === 'PropertyLoad') { @@ -262,7 +295,7 @@ function getMaybeNonNullInInstruction( function isImmutableAtInstr( identifier: Identifier, instr: InstructionId, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): boolean { if (context.nestedFnImmutableContext != null) { /** @@ -295,22 +328,9 @@ function isImmutableAtInstr( } } -type CollectNonNullsInBlocksContext = { - temporaries: ReadonlyMap; - knownImmutableIdentifiers: ReadonlySet; - hoistableFromOptionals: ReadonlyMap; - registry: PropertyPathRegistry; - /** - * (For nested / inner function declarations) - * Context variables (i.e. captured from an outer scope) that are immutable. - * Note that this technically could be merged into `knownImmutableIdentifiers`, - * but are currently kept separate for readability. - */ - nestedFnImmutableContext: ReadonlySet | null; -}; function collectNonNullsInBlocks( fn: HIRFunction, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { /** * Known non-null objects such as functional component props can be safely @@ -358,18 +378,22 @@ function collectNonNullsInBlocks( new Set(), ); const innerOptionals = collectOptionalChainSidemap(innerFn.func); - const innerHoistableMap = collectHoistablePropertyLoads( + const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, - innerTemporaries, - innerOptionals.hoistableObjects, - context.nestedFnImmutableContext ?? - new Set( - innerFn.func.context - .filter(place => - isImmutableAtInstr(place.identifier, instr.id, context), - ) - .map(place => place.identifier.id), - ), + { + ...context, + temporaries: innerTemporaries, // TODO: remove in later PR + hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR + nestedFnImmutableContext: + context.nestedFnImmutableContext ?? + new Set( + innerFn.func.context + .filter(place => + isImmutableAtInstr(place.identifier, instr.id, context), + ) + .map(place => place.identifier.id), + ), + }, ); const innerHoistables = assertNonNull( innerHoistableMap.get(innerFn.func.body.entry), diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 855ca9121d..0178aea6e4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -46,7 +46,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void { const hoistablePropertyLoads = keyByScopeId( fn, - collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null), + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects), ); const scopeDeps = collectDependencies( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md new file mode 100644 index 0000000000..73df2b615b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(5); + const { data } = t0; + let t1; + if ($[0] !== data.a.d) { + t1 = () => data.a.d; + $[0] = data.a.d; + $[1] = t1; + } else { + t1 = $[1]; + } + const t2 = data.a?.b.c; + let t3; + if ($[2] !== t1 || $[3] !== t2) { + t3 = ; + $[2] = t1; + $[3] = t2; + $[4] = t3; + } else { + t3 = $[4]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ data: { a: null } }], + sequentialRenders: [{ data: { a: { b: { c: 4 } } } }], +}; + +``` + +### Eval output +(kind: ok)
{"foo":{"kind":"Function"},"bar":4,"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx new file mode 100644 index 0000000000..05ed136d5f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx @@ -0,0 +1,14 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; From 640c52ef9a075cadc40776e20ca3e9efd584e1e6 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 044/422] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR ' --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1869 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index b85d9425cb..4c57e792e3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,16 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..352552bf02 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 0d4afc594107049e3380c3fabb32d1503ea47d74 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 045/422] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200 Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From 754d9338c1540b5e6ed08e67684f5d147a1a8da5 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 046/422] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 4c57e792e3..31e42049fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,6 +230,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 02e60eff91..ed56ff0681 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 157e2de81a..bb99a5d90f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From 857947fda6371e0815d7a60144c95e524f54ac43 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 047/422] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a179224a77..a772be62aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From ded2bf25402ba0da5c9abbe0dba53c375288ce33 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 048/422] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From 24f726629a103fb64e13bfb39bcef40333a4d490 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 049/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 36 files changed, 111 insertions(+), 332 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..3248775f7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,7 +231,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 60152cad6654b1a2e34791a22abca0d57586eb07 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 050/422] [compiler][be] Clean up nested function context in DCE Now that we rely on function context exclusively, let's clean up `HIRFunction.context` after DCE. This PR is in preparation of #31204, which would otherwise have unnecessary declarations (of context values that become entirely DCE'd) ' --- .../src/Optimization/DeadCodeElimination.ts | 8 ++++ .../compiler/arrow-expr-directive.expect.md | 5 ++- .../compiler/capture-param-mutate.expect.md | 9 ++-- .../function-expr-directive.expect.md | 5 ++- .../compiler/merge-scopes-callback.expect.md | 5 ++- ...reactive-scope-with-early-return.expect.md | 42 ++++++++----------- ...react-hooks-based-on-import-name.expect.md | 5 ++- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 885ec2b3ab..0202d3ecf0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103..93eb2bd28a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c..9e4709616d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md index 3980434bde..8c4aa612e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md @@ -34,7 +34,7 @@ function Component() { t0 = function update() { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -51,6 +51,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md index edf748de5c..0ff9773f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md @@ -32,7 +32,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -61,6 +61,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index 0c1bf1cd70..506e4ca713 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -39,7 +39,7 @@ function Component() { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions function Component() { - const $ = _c(8); + const $ = _c(7); const items = useItems(); let t0; let t1; @@ -47,35 +47,25 @@ function Component() { if ($[0] !== items) { t2 = Symbol.for("react.early_return_sentinel"); bb0: { - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = (t4) => { - const [item] = t4; - return item.name != null; - }; - $[4] = t3; - } else { - t3 = $[4]; - } - t0 = items.filter(t3); + t0 = items.filter(_temp); const filteredItems = t0; if (filteredItems.length === 0) { - let t4; - if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t4 = ( + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (
); - $[5] = t4; + $[4] = t3; } else { - t4 = $[5]; + t3 = $[4]; } - t2 = t4; + t2 = t3; break bb0; } - t1 = filteredItems.map(_temp); + t1 = filteredItems.map(_temp2); } $[0] = items; $[1] = t1; @@ -90,19 +80,23 @@ function Component() { return t2; } let t3; - if ($[6] !== t1) { + if ($[5] !== t1) { t3 = <>{t1}; - $[6] = t1; - $[7] = t3; + $[5] = t1; + $[6] = t3; } else { - t3 = $[7]; + t3 = $[6]; } return t3; } -function _temp(t0) { +function _temp2(t0) { const [item_0] = t0; return ; } +function _temp(t0) { + const [item] = t0; + return item.name != null; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md index dc3081321e..496d61df9d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md @@ -38,7 +38,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -67,6 +67,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, From a392efd887d82c92e9808bb36e246da2af7e705e Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 25 Oct 2024 11:36:53 -0700 Subject: [PATCH 051/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../src/SSA/EliminateRedundantPhi.ts | 20 +++ .../src/SSA/EnterSSA.ts | 3 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 38 files changed, 131 insertions(+), 335 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..3248775f7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,7 +231,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..4e8dc74419 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -7,12 +7,15 @@ import {CompilerError} from '../CompilerError'; import {BlockId, HIRFunction, Identifier, Place} from '../HIR/HIR'; +import {printHIR, printIdentifier} from '../HIR/PrintHIR'; import { eachInstructionLValue, eachInstructionOperand, eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +144,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 6b5eba2d14c47fe0a986f1e19f1a534222d4b9eb Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 25 Oct 2024 11:36:53 -0700 Subject: [PATCH 052/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../src/SSA/EliminateRedundantPhi.ts | 19 +++ .../src/SSA/EnterSSA.ts | 3 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 38 files changed, 130 insertions(+), 335 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..3248775f7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,7 +231,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..37394daa8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -13,6 +13,8 @@ import { eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +143,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 31f569635681521ec6bce8c9cad3ccdadd2c123c Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 25 Oct 2024 20:58:00 -0600 Subject: [PATCH 053/422] [compiler][be] Stabilize compiler output: sort deps and decls by name All dependencies and declarations of a reactive scope can be reordered to scope start/end. i.e. generated code does not depend on conditional short-circuiting logic as dependencies are inferred to have no side effects. Sorting these by name helps us get higher signal compilation snapshot diffs when upgrading the compiler and testing PRs internally at Meta --- .../ReactiveScopes/CodegenReactiveFunction.ts | 36 +++++++++++++++++-- .../compiler/array-at-effect.expect.md | 6 ++-- .../compiler/array-property-call.expect.md | 10 +++--- .../bug-invalid-phi-as-dependency.expect.md | 6 ++-- ...fun-alias-captured-mutate-2-iife.expect.md | 6 ++-- ...ring-fun-alias-captured-mutate-2.expect.md | 6 ++-- ...alias-captured-mutate-arr-2-iife.expect.md | 6 ++-- ...-fun-alias-captured-mutate-arr-2.expect.md | 6 ++-- ...c-alias-captured-mutate-arr-iife.expect.md | 6 ++-- ...g-func-alias-captured-mutate-arr.expect.md | 6 ++-- ...-func-alias-captured-mutate-iife.expect.md | 6 ++-- ...uring-func-alias-captured-mutate.expect.md | 6 ++-- ...turing-function-member-expr-call.expect.md | 6 ++-- .../fixtures/compiler/component.expect.md | 12 +++---- .../compiler/dependencies-outputs.expect.md | 6 ++-- .../destructure-in-branch-ssa.expect.md | 8 ++--- ...g-same-property-identifier-names.expect.md | 6 ++-- .../fixtures/compiler/destructuring.expect.md | 34 +++++++++--------- ...er-declaration-of-previous-scope.expect.md | 10 +++--- .../existing-variables-with-c-name.expect.md | 6 ++-- .../compiler/fast-refresh-reloading.expect.md | 6 ++-- ...-mutable-range-destructured-prop.expect.md | 6 ++-- ...btparam-with-jsx-element-content.expect.md | 6 ++-- ...onmutating-loop-local-collection.expect.md | 6 ++-- ...pression-prototype-call-mutating.expect.md | 6 ++-- ...unctionexpr-conditional-access-2.expect.md | 6 ++-- .../functionexpr–conditional-access.expect.md | 6 ++-- ...bals-dont-resolve-local-useState.expect.md | 6 ++-- .../fixtures/compiler/hook-noAlias.expect.md | 6 ++-- .../compiler/hooks-with-prefix.expect.md | 6 ++-- ...incompatible-destructuring-kinds.expect.md | 14 ++++---- ...-promoted-to-outer-scope-dynamic.expect.md | 20 +++++------ ...jsx-outlining-child-stored-in-id.expect.md | 12 +++---- .../jsx-outlining-jsx-stored-in-id.expect.md | 18 +++++----- .../jsx-outlining-separate-nested.expect.md | 22 ++++++------ .../compiler/jsx-outlining-simple.expect.md | 18 +++++----- ...-tag-evaluation-order-non-global.expect.md | 10 +++--- .../lambda-capture-returned-alias.expect.md | 6 ++-- .../lower-context-acess-multiple.expect.md | 6 ++-- .../lower-context-selector-simple.expect.md | 6 ++-- ...ge-consecutive-scopes-reordering.expect.md | 6 ++-- .../compiler/method-call-computed.expect.md | 8 ++--- .../compiler/method-call-fn-call.expect.md | 8 ++--- .../fixtures/compiler/method-call.expect.md | 6 ++-- ...pture-in-unsplittable-memo-block.expect.md | 10 +++--- .../object-shorthand-method-nested.expect.md | 6 ++-- ...al-member-expression-as-memo-dep.expect.md | 6 ++-- ...ession-single-with-unconditional.expect.md | 6 ++-- ...ptional-member-expression-single.expect.md | 6 ++-- ...ession-with-conditional-optional.expect.md | 12 +++---- ...mber-expression-with-conditional.expect.md | 12 +++---- ...rly-return-within-reactive-scope.expect.md | 10 +++--- ...Callback-in-other-reactive-block.expect.md | 8 ++--- ...k-reordering-deplist-controlflow.expect.md | 16 ++++----- ...useMemo-conditional-access-alloc.expect.md | 6 ++-- ...eMemo-conditional-access-noAlloc.expect.md | 6 ++-- .../useMemo-in-other-reactive-block.expect.md | 8 ++--- ...-reordering-depslist-controlflow.expect.md | 6 ++-- ...signed-loop-force-scopes-enabled.expect.md | 8 ++--- ...al-member-expression-as-memo-dep.expect.md | 6 ++-- ...ession-single-with-unconditional.expect.md | 6 ++-- ...ptional-member-expression-single.expect.md | 6 ++-- ...rly-return-within-reactive-scope.expect.md | 10 +++--- ...r-function-cond-access-local-var.expect.md | 6 ++-- ...function-cond-access-not-hoisted.expect.md | 6 ++-- ...n-uncond-access-hoists-other-dep.expect.md | 6 ++-- ...uncond-optional-hoists-other-dep.expect.md | 12 +++---- .../promote-uncond.expect.md | 8 ++--- .../switch-non-final-default.expect.md | 16 ++++----- .../switch.expect.md | 16 ++++----- ...-analysis-interleaved-reactivity.expect.md | 6 ++-- ...ve-via-mutation-of-computed-load.expect.md | 6 ++-- ...ve-via-mutation-of-property-load.expect.md | 6 ++-- .../reassignment-separate-scopes.expect.md | 16 ++++----- ...eactive-cond-deps-break-in-scope.expect.md | 6 ++-- ...active-cond-deps-return-in-scope.expect.md | 16 ++++----- ...function-cond-access-not-hoisted.expect.md | 6 ++-- .../hoist-deps-diff-ssa-instance.expect.md | 6 ++-- .../jump-poisoned/break-in-scope.expect.md | 6 ++-- .../loop-break-in-scope.expect.md | 6 ++-- ...e-if-nonexhaustive-poisoned-deps.expect.md | 10 +++--- ...-if-nonexhaustive-poisoned-deps1.expect.md | 10 +++--- .../jump-poisoned/return-in-scope.expect.md | 16 ++++----- .../return-poisons-outer-scope.expect.md | 10 +++--- ...p-target-within-scope-loop-break.expect.md | 6 ++-- ...e-if-exhaustive-nonpoisoned-deps.expect.md | 10 +++--- ...-if-exhaustive-nonpoisoned-deps1.expect.md | 10 +++--- .../promote-uncond.expect.md | 6 ++-- ...duce-if-exhaustive-poisoned-deps.expect.md | 18 +++++----- .../subpath-order1.expect.md | 6 ++-- .../superpath-order1.expect.md | 6 ++-- .../uncond-access-in-mutable-range.expect.md | 6 ++-- .../reordering-across-blocks.expect.md | 6 ++-- ...ed-property-load-for-method-call.expect.md | 6 ++-- ...uned-scope-leaks-value-via-alias.expect.md | 6 ++-- ...invalid-pruned-scope-leaks-value.expect.md | 6 ++-- ...o-invalid-reactivity-value-block.expect.md | 6 ++-- ...lack-of-phi-types-explicit-types.expect.md | 6 ++-- ...ng-memoization-lack-of-phi-types.expect.md | 6 ++-- .../repro-no-value-for-temporary.expect.md | 6 ++-- ...ro-propagate-type-of-ternary-jsx.expect.md | 8 ++--- ...epro-slow-validate-preserve-memo.expect.md | 6 ++-- ...ble-code-early-return-in-useMemo.expect.md | 6 ++-- .../fixtures/compiler/repro.expect.md | 10 +++--- .../rest-param-with-array-pattern.expect.md | 6 ++-- .../rest-param-with-identifier.expect.md | 6 ++-- ...param-with-object-spread-pattern.expect.md | 6 ++-- ...s-dep-and-redeclare-maybe-frozen.expect.md | 14 ++++---- ...me-variable-as-dep-and-redeclare.expect.md | 24 ++++++------- ...assignment-to-scope-declarations.expect.md | 14 ++++---- ...ixed-local-and-scope-declaration.expect.md | 14 ++++---- .../switch-non-final-default.expect.md | 16 ++++----- .../fixtures/compiler/switch.expect.md | 16 ++++----- .../todo.jsx-outlining-children.expect.md | 12 +++---- ...odo.jsx-outlining-duplicate-prop.expect.md | 12 +++---- ...ntext-access-array-destructuring.expect.md | 6 ++-- ...text-access-destructure-multiple.expect.md | 6 ++-- ...r-context-access-mixed-array-obj.expect.md | 6 ++-- ...text-access-nested-destructuring.expect.md | 6 ++-- ...wer-context-access-property-load.expect.md | 6 ++-- .../try-catch-in-nested-scope.expect.md | 16 ++++----- .../try-catch-with-catch-param.expect.md | 10 +++--- .../compiler/try-catch-with-return.expect.md | 10 +++--- ...type-provider-log-default-import.expect.md | 12 +++---- .../compiler/type-provider-log.expect.md | 12 +++---- ...r-store-capture-namespace-import.expect.md | 20 +++++------ .../type-provider-store-capture.expect.md | 20 +++++------ .../fixtures/compiler/unary-expr.expect.md | 24 ++++++------- .../use-operator-call-expression.expect.md | 6 ++-- .../use-operator-conditional.expect.md | 6 ++-- .../use-operator-method-call.expect.md | 6 ++-- .../useEffect-nested-lambdas.expect.md | 6 ++-- .../useState-unpruned-dependency.expect.md | 6 ++-- 133 files changed, 633 insertions(+), 601 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 297c771254..1841c5322d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -572,7 +572,22 @@ function codegenReactiveScope( const changeExpressions: Array = []; const changeExpressionComments: Array = []; const outputComments: Array = []; - for (const dep of scope.dependencies) { + const sortedDeps = [...scope.dependencies].sort((a, b) => { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && + b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected dependency to be named', + loc: a.identifier.loc, + }, + ); + const aName = a.identifier.name.value; + const bName = b.identifier.name.value; + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; + }); + for (const dep of sortedDeps) { const index = cx.nextCacheIndex; changeExpressionComments.push(printDependencyComment(dep)); const comparison = t.binaryExpression( @@ -615,7 +630,24 @@ function codegenReactiveScope( ); } let firstOutputIndex: number | null = null; - for (const [, {identifier}] of scope.declarations) { + // TODO Map + + const sortedDecls = [...scope.declarations].sort(([, a], [, b]) => { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && + b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected dependency to be named', + loc: a.identifier.loc, + }, + ); + const aName = a.identifier.name.value; + const bName = b.identifier.name.value; + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; + }); + for (const [, {identifier}] of sortedDecls) { const index = cx.nextCacheIndex; if (firstOutputIndex === null) { firstOutputIndex = index; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md index 3aa51ba6d6..a8bad51215 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md @@ -41,7 +41,7 @@ function ArrayAtTest(props) { } const arr = t1; let t2; - if ($[4] !== props.y || $[5] !== arr) { + if ($[4] !== arr || $[5] !== props.y) { let t3; if ($[7] !== props.y) { t3 = bar(props.y); @@ -51,8 +51,8 @@ function ArrayAtTest(props) { t3 = $[8]; } t2 = arr.at(t3); - $[4] = props.y; - $[5] = arr; + $[4] = arr; + $[5] = props.y; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md index 7aa47c5803..6618be4a6c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md @@ -24,18 +24,18 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(11); - let t0; let a; + let t0; if ($[0] !== props.a || $[1] !== props.b) { a = [props.a, props.b, "hello"]; t0 = a.push(42); $[0] = props.a; $[1] = props.b; - $[2] = t0; - $[3] = a; + $[2] = a; + $[3] = t0; } else { - t0 = $[2]; - a = $[3]; + a = $[2]; + t0 = $[3]; } const x = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md index 32e2c9fd64..09d2d8800b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md @@ -70,10 +70,10 @@ function Component() { throw new Error("invariant broken"); } let t1; - if ($[1] !== obj || $[2] !== boxedInner) { + if ($[1] !== boxedInner || $[2] !== obj) { t1 = ; - $[1] = obj; - $[2] = boxedInner; + $[1] = boxedInner; + $[2] = obj; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md index 65ab9c277c..5e0b32709b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md @@ -32,7 +32,7 @@ import { mutate } from "shared-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md index 170f68bade..f9ce3f2e98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md index e315cd401d..81737a1ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md index 7371f3d27a..38590d1559 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md index cc41adcbbc..4882aa822f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md index 34f6a55740..7c94c33e49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md index b2d7048756..60493dd25a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..14532562fb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md index 51679299fe..cab9c9a500 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md @@ -46,10 +46,10 @@ function component(t0) { } const hide = t2; let t3; - if ($[4] !== poke || $[5] !== hide) { + if ($[4] !== hide || $[5] !== poke) { t3 = ; - $[4] = poke; - $[5] = hide; + $[4] = hide; + $[5] = poke; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md index 80d6e6df8c..c6037fd2bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md @@ -46,7 +46,7 @@ function Component(props) { const items = props.items; const maxItems = props.maxItems; let renderedItems; - if ($[0] !== maxItems || $[1] !== items) { + if ($[0] !== items || $[1] !== maxItems) { renderedItems = []; const seen = new Set(); const max = Math.max(0, maxItems); @@ -62,8 +62,8 @@ function Component(props) { break; } } - $[0] = maxItems; - $[1] = items; + $[0] = items; + $[1] = maxItems; $[2] = renderedItems; } else { renderedItems = $[2]; @@ -79,15 +79,15 @@ function Component(props) { t0 = $[4]; } let t1; - if ($[5] !== t0 || $[6] !== renderedItems) { + if ($[5] !== renderedItems || $[6] !== t0) { t1 = (
{t0} {renderedItems}
); - $[5] = t0; - $[6] = renderedItems; + $[5] = renderedItems; + $[6] = t0; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md index 6fc686cb19..f0f9911c07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md @@ -41,7 +41,7 @@ function foo(a, b) { x = $[1]; } let y; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { y = []; if (x.length) { y.push(x); @@ -49,8 +49,8 @@ function foo(a, b) { if (b) { y.push(b); } - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = y; } else { y = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md index d65082cbc8..b159789106 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md @@ -61,11 +61,11 @@ function useFoo(props) { z = $[4]; } let t0; - if ($[5] !== x || $[6] !== y || $[7] !== myList) { + if ($[5] !== myList || $[6] !== x || $[7] !== y) { t0 = { x, y, myList }; - $[5] = x; - $[6] = y; - $[7] = myList; + $[5] = myList; + $[6] = x; + $[7] = y; $[8] = t0; } else { t0 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md index b86498b922..3e1c4771ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md @@ -41,10 +41,10 @@ function Component(props) { } const sameName = t1; let t2; - if ($[2] !== sameName || $[3] !== renamed) { + if ($[2] !== renamed || $[3] !== sameName) { t2 = [sameName, renamed]; - $[2] = sameName; - $[3] = renamed; + $[2] = renamed; + $[3] = sameName; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md index c175cc558c..f292e83e16 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md @@ -36,45 +36,45 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(18); - let t0; let d; let h; + let t0; if ($[0] !== a) { [d, t0, ...h] = a; $[0] = a; - $[1] = t0; - $[2] = d; - $[3] = h; + $[1] = d; + $[2] = h; + $[3] = t0; } else { - t0 = $[1]; - d = $[2]; - h = $[3]; + d = $[1]; + h = $[2]; + t0 = $[3]; } const [t1] = t0; - let t2; let g; + let t2; if ($[4] !== t1) { ({ e: t2, ...g } = t1); $[4] = t1; - $[5] = t2; - $[6] = g; + $[5] = g; + $[6] = t2; } else { - t2 = $[5]; - g = $[6]; + g = $[5]; + t2 = $[6]; } const { f } = t2; const { l: t3, p } = b; const { m: t4 } = t3; - let t5; let o; + let t5; if ($[7] !== t4) { [t5, ...o] = t4; $[7] = t4; - $[8] = t5; - $[9] = o; + $[8] = o; + $[9] = t5; } else { - t5 = $[8]; - o = $[9]; + o = $[8]; + t5 = $[9]; } const [n] = t5; let t6; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md index 29780eb76c..ce5bfda644 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md @@ -53,8 +53,8 @@ import { ValidateMemoization } from "shared-runtime"; function Component(t0) { const $ = _c(25); const { a, b, c } = t0; - let y; let x; + let y; if ($[0] !== a || $[1] !== b || $[2] !== c) { x = []; if (a) { @@ -73,11 +73,11 @@ function Component(t0) { $[0] = a; $[1] = b; $[2] = c; - $[3] = y; - $[4] = x; + $[3] = x; + $[4] = y; } else { - y = $[3]; - x = $[4]; + x = $[3]; + y = $[4]; } let t1; if ($[7] !== y) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md index 0d671a3de2..5cde3bde23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md @@ -61,10 +61,10 @@ function Component(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== array) { + if ($[4] !== array || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = array; + $[4] = array; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md index ecd03a0b10..4175d23fda 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md @@ -59,10 +59,10 @@ function Component(props) { t3 = $[4]; } let t4; - if ($[5] !== t3 || $[6] !== doubled) { + if ($[5] !== doubled || $[6] !== t3) { t4 = ; - $[5] = t3; - $[6] = doubled; + $[5] = doubled; + $[6] = t3; $[7] = t4; } else { t4 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md index d56f7a98ad..9bb651aa67 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md @@ -61,10 +61,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== el) { + if ($[4] !== el || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = el; + $[4] = el; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md index d58c25b510..56ffb70cb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md @@ -32,7 +32,7 @@ function Component(t0) { const $ = _c(4); const { name, data, icon } = t0; let t1; - if ($[0] !== name || $[1] !== icon || $[2] !== data) { + if ($[0] !== data || $[1] !== icon || $[2] !== name) { t1 = ( {fbt._( @@ -61,9 +61,9 @@ function Component(t0) { )} ); - $[0] = name; + $[0] = data; $[1] = icon; - $[2] = data; + $[2] = name; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md index cff8c5bd13..4abe630044 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md @@ -91,10 +91,10 @@ function Component(t0) { t5 = $[9]; } let t6; - if ($[10] !== x || $[11] !== b) { + if ($[10] !== b || $[11] !== x) { t6 = [x, b]; - $[10] = x; - $[11] = b; + $[10] = b; + $[11] = x; $[12] = t6; } else { t6 = $[12]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md index be59673c15..9888e96222 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md @@ -59,10 +59,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== a_0) { + if ($[4] !== a_0 || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = a_0; + $[4] = a_0; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..32498e1379 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -36,10 +36,10 @@ function Component(t0) { } const f = t1; let t2; - if ($[2] !== props || $[3] !== f) { + if ($[2] !== f || $[3] !== props) { t2 = props == null ? _temp : f; - $[2] = props; - $[3] = f; + $[2] = f; + $[3] = props; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..4a62bf6f24 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -36,10 +36,10 @@ function Component(props) { } const getLength = t0; let t1; - if ($[2] !== props.bar || $[3] !== getLength) { + if ($[2] !== getLength || $[3] !== props.bar) { t1 = props.bar && getLength(); - $[2] = props.bar; - $[3] = getLength; + $[2] = getLength; + $[3] = props.bar; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md index 7548a3b639..be7f3f1bd2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md @@ -56,10 +56,10 @@ function Component() { t0 = $[1]; } let t1; - if ($[2] !== t0 || $[3] !== state) { + if ($[2] !== state || $[3] !== t0) { t1 =
{state}
; - $[2] = t0; - $[3] = state; + $[2] = state; + $[3] = t0; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md index 96ccd1e2f1..329d57a035 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md @@ -41,10 +41,10 @@ function Component(props) { console.log(props); }, [props.a]); let t1; - if ($[2] !== x || $[3] !== item) { + if ($[2] !== item || $[3] !== x) { t1 = [x, item]; - $[2] = x; - $[3] = item; + $[2] = item; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md index f7e02a53f1..085df625f5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md @@ -73,15 +73,15 @@ function Component() { t1 = $[4]; } let t3; - if ($[5] !== t2 || $[6] !== json) { + if ($[5] !== json || $[6] !== t2) { t3 = (
{t2} {json}
); - $[5] = t2; - $[6] = json; + $[5] = json; + $[6] = t2; $[7] = t3; } else { t3 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md index 970cc50f03..8afc59a80b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md @@ -29,22 +29,22 @@ import { Stringify } from "shared-runtime"; function Component(t0) { const $ = _c(4); - let t1; let a; let b; + let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { a = "a"; const [t2, t3] = [null, null]; t1 = t3; a = t2; - $[0] = t1; - $[1] = a; - $[2] = b; + $[0] = a; + $[1] = b; + $[2] = t1; } else { - t1 = $[0]; - a = $[1]; - b = $[2]; + a = $[0]; + b = $[1]; + t1 = $[2]; } b = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md index becc4066e7..735462657f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md @@ -27,10 +27,10 @@ function Component(props) { const $ = _c(15); const item = useFragment(FRAGMENT, props.item); useFreeze(item); - let t0; let T0; - let t1; let T1; + let t0; + let t1; if ($[0] !== item) { const count = new MaybeMutable(item); @@ -44,15 +44,15 @@ function Component(props) { } t0 = maybeMutate(count); $[0] = item; - $[1] = t0; - $[2] = T0; - $[3] = t1; - $[4] = T1; + $[1] = T0; + $[2] = T1; + $[3] = t0; + $[4] = t1; } else { - t0 = $[1]; - T0 = $[2]; - t1 = $[3]; - T1 = $[4]; + T0 = $[1]; + T1 = $[2]; + t0 = $[3]; + t1 = $[4]; } let t2; if ($[6] !== t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..b84229156b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -83,10 +83,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -98,15 +98,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md index 496282e1ef..7fca963134 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md @@ -52,7 +52,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -66,8 +66,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -94,10 +94,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -109,15 +109,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md index 7f86546cd4..9d2b254c06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md @@ -60,7 +60,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -73,8 +73,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -117,7 +117,7 @@ function _temp(t0) { t3 = $[5]; } let t4; - if ($[6] !== x || $[7] !== t1 || $[8] !== t2 || $[9] !== t3) { + if ($[6] !== t1 || $[7] !== t2 || $[8] !== t3 || $[9] !== x) { t4 = ( {t1} @@ -125,10 +125,10 @@ function _temp(t0) { {t3} ); - $[6] = x; - $[7] = t1; - $[8] = t2; - $[9] = t3; + $[6] = t1; + $[7] = t2; + $[8] = t3; + $[9] = x; $[10] = t4; } else { t4 = $[10]; @@ -140,15 +140,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md index 9e268227a2..09323f5ac6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -63,8 +63,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,10 +91,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -106,15 +106,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md index b1dfbc61c3..aeb0cdf88c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md @@ -53,8 +53,8 @@ function maybeMutate(x) {} function Component(props) { const $ = _c(11); - let Tag; let T0; + let Tag; let t0; if ($[0] !== props.component || $[1] !== props.alternateComponent) { const maybeMutable = new MaybeMutable(); @@ -64,12 +64,12 @@ function Component(props) { t0 = ((Tag = props.alternateComponent), maybeMutate(maybeMutable)); $[0] = props.component; $[1] = props.alternateComponent; - $[2] = Tag; - $[3] = T0; + $[2] = T0; + $[3] = Tag; $[4] = t0; } else { - Tag = $[2]; - T0 = $[3]; + T0 = $[2]; + Tag = $[3]; t0 = $[4]; } let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md index e172ee1039..bac21217c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md @@ -44,7 +44,7 @@ function CaptureNotMutate(props) { } const idx = t0; let aliasedElement; - if ($[2] !== props.el || $[3] !== idx) { + if ($[2] !== idx || $[3] !== props.el) { const element = bar(props.el); const fn = function () { @@ -54,8 +54,8 @@ function CaptureNotMutate(props) { aliasedElement = fn(); mutate(aliasedElement); - $[2] = props.el; - $[3] = idx; + $[2] = idx; + $[3] = props.el; $[4] = aliasedElement; } else { aliasedElement = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md index 47c7a2d743..af9b1df36a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md @@ -21,10 +21,10 @@ function App() { const { foo } = useContext_withSelector(MyContext, _temp); const { bar } = useContext_withSelector(MyContext, _temp2); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md index 0b12c2e250..d13682467b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md @@ -19,10 +19,10 @@ function App() { const $ = _c(3); const { foo, bar } = useContext_withSelector(MyContext, _temp); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md index 5bf1f2cf4d..e5a9081137 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md @@ -60,7 +60,7 @@ function Component() { t2 = $[3]; } let t3; - if ($[4] !== t1 || $[5] !== t0) { + if ($[4] !== t0 || $[5] !== t1) { t3 = (
{t2} @@ -68,8 +68,8 @@ function Component() { {t0}
); - $[4] = t1; - $[5] = t0; + $[4] = t0; + $[5] = t1; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md index 887479c01e..2fc302c8b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md @@ -43,11 +43,11 @@ function foo(a, b, c) { } const y = t1; let t2; - if ($[4] !== x || $[5] !== y.method || $[6] !== b) { + if ($[4] !== b || $[5] !== x || $[6] !== y.method) { t2 = x[y.method](b); - $[4] = x; - $[5] = y.method; - $[6] = b; + $[4] = b; + $[5] = x; + $[6] = y.method; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md index c32777534b..58c06101d3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md @@ -33,11 +33,11 @@ function foo(a, b, c) { const method = x.method; let t1; - if ($[2] !== method || $[3] !== x || $[4] !== b) { + if ($[2] !== b || $[3] !== method || $[4] !== x) { t1 = method.call(x, b); - $[2] = method; - $[3] = x; - $[4] = b; + $[2] = b; + $[3] = method; + $[4] = x; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md index ad45287d1f..fd8a4935a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md @@ -40,10 +40,10 @@ function foo(a, b, c) { } const x = t0; let t1; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { t1 = x.foo(b); - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md index 090e9d889c..a3403e6c8d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md @@ -73,8 +73,8 @@ import { identity, mutate } from "shared-runtime"; function useFoo(t0) { const $ = _c(4); const { a, b } = t0; - let z; let y; + let z; if ($[0] !== a || $[1] !== b) { const x = { a }; y = {}; @@ -83,11 +83,11 @@ function useFoo(t0) { mutate(y); $[0] = a; $[1] = b; - $[2] = z; - $[3] = y; + $[2] = y; + $[3] = z; } else { - z = $[2]; - y = $[3]; + y = $[2]; + z = $[3]; } if (z[0] !== y) { throw new Error("oh no!"); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md index 4b500f52d6..dc1cd699d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md @@ -40,7 +40,7 @@ function useHook(t0) { const { value } = t0; const [state] = useState(false); let t1; - if ($[0] !== value || $[1] !== state) { + if ($[0] !== state || $[1] !== value) { t1 = { getX() { return { @@ -52,8 +52,8 @@ function useHook(t0) { }; }, }; - $[0] = value; - $[1] = state; + $[0] = state; + $[1] = value; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md index 77875f789d..3dd8e73032 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md index 46767056bd..3cd9877813 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md index 6e44a97b45..60a6171ab1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md index 77ded20d93..2674d78c99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md index 10c23085d8..1d4a50d285 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..42b3b21a20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); - let y; let t0; + let y; if ($[0] !== props) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -57,11 +57,11 @@ function Component(props) { } } $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md index 9ca63e23c4..3da133e929 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(7); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; if ($[4] !== minWidth || $[5] !== width) { @@ -55,9 +55,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md index 947e3bd2eb..ee4e4634cb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md @@ -42,9 +42,9 @@ import { Stringify } from "shared-runtime"; function Foo(t0) { const $ = _c(8); const { arr1, arr2, foo } = t0; - let t1; let getVal1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + let t1; + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -55,13 +55,13 @@ function Foo(t0) { t1 = () => [y]; foo ? (y = x.concat(arr2)) : y; $[0] = arr1; - $[1] = foo; - $[2] = arr2; - $[3] = t1; - $[4] = getVal1; + $[1] = arr2; + $[2] = foo; + $[3] = getVal1; + $[4] = t1; } else { - t1 = $[3]; - getVal1 = $[4]; + getVal1 = $[3]; + t1 = $[4]; } const getVal2 = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md index 3a7016f803..f7353ddd5e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md @@ -44,10 +44,10 @@ function Component(t0) { t3 = $[1]; } let t4; - if ($[2] !== t3 || $[3] !== propA) { + if ($[2] !== propA || $[3] !== t3) { t4 = { value: t3, other: propA }; - $[2] = t3; - $[3] = propA; + $[2] = propA; + $[3] = t3; $[4] = t4; } else { t4 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md index f0ce9b9114..c6ad9bcdac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md @@ -34,10 +34,10 @@ function Component(t0) { const t2 = propB?.x.y; let t3; - if ($[0] !== t2 || $[1] !== propA) { + if ($[0] !== propA || $[1] !== t2) { t3 = { value: t2, other: propA }; - $[0] = t2; - $[1] = propA; + $[0] = propA; + $[1] = t2; $[2] = t3; } else { t3 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md index 9d7feacd6d..7a27bb8521 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(6); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; @@ -58,9 +58,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md index e9d2bffb30..5fc0ec510b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md @@ -44,7 +44,7 @@ function Foo(t0) { const { arr1, arr2, foo } = t0; let t1; let val1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -63,8 +63,8 @@ function Foo(t0) { foo ? (y = x.concat(arr2)) : y; t1 = (() => [y])(); $[0] = arr1; - $[1] = foo; - $[2] = arr2; + $[1] = arr2; + $[2] = foo; $[3] = t1; $[4] = val1; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md index 594e68f24f..de39fc9706 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md @@ -32,15 +32,15 @@ function Component(t0) { const $ = _c(5); const { base, start, increment, test } = t0; let value; - if ($[0] !== base || $[1] !== start || $[2] !== test || $[3] !== increment) { + if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) { value = base; for (let i = start; i < test; i = i + increment, i) { value = value + i; } $[0] = base; - $[1] = start; - $[2] = test; - $[3] = increment; + $[1] = increment; + $[2] = start; + $[3] = test; $[4] = value; } else { value = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md index d0486cd8c2..28612f2d73 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md index b4a55fcb61..2861ab71c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md index f15b9b8e9b..b5db44aa2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md index 324eb714fc..acc72529ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md @@ -32,8 +32,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(6); - let y; let t0; + let y; if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -60,11 +60,11 @@ function Component(props) { $[0] = props.cond; $[1] = props.a; $[2] = props.b; - $[3] = y; - $[4] = t0; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md index c0f8aa97cd..673dd0d5fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md @@ -58,7 +58,7 @@ function useFoo(t0) { local = $[1]; } let t1; - if ($[2] !== shouldReadA || $[3] !== local) { + if ($[2] !== local || $[3] !== shouldReadA) { t1 = ( { @@ -70,8 +70,8 @@ function useFoo(t0) { shouldInvokeFns={true} /> ); - $[2] = shouldReadA; - $[3] = local; + $[2] = local; + $[3] = shouldReadA; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md index e37b8365a2..abf4c98f23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md @@ -41,7 +41,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a) { + if ($[0] !== a || $[1] !== shouldReadA) { t1 = ( { @@ -53,8 +53,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a; + $[0] = a; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md index 89b4d281f8..d82956e4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md @@ -51,13 +51,13 @@ function Foo(t0) { const fn = t1; useIdentity(null); let x; - if ($[2] !== cond || $[3] !== a.b.c) { + if ($[2] !== a.b.c || $[3] !== cond) { x = makeArray(); if (cond) { x.push(identity(a.b.c)); } - $[2] = cond; - $[3] = a.b.c; + $[2] = a.b.c; + $[3] = cond; $[4] = x; } else { x = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md index 591e04de7b..c81e59ecea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md @@ -50,22 +50,22 @@ function Foo(t0) { const fn = t1; useIdentity(null); let arr; - if ($[2] !== cond || $[3] !== a.b?.c.e) { + if ($[2] !== a.b?.c.e || $[3] !== cond) { arr = makeArray(); if (cond) { arr.push(identity(a.b?.c.e)); } - $[2] = cond; - $[3] = a.b?.c.e; + $[2] = a.b?.c.e; + $[3] = cond; $[4] = arr; } else { arr = $[4]; } let t2; - if ($[5] !== fn || $[6] !== arr) { + if ($[5] !== arr || $[6] !== fn) { t2 = ; - $[5] = fn; - $[6] = arr; + $[5] = arr; + $[6] = fn; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md index 902a1578c8..30cea1e2c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md @@ -38,15 +38,15 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(4); let x; - if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { + if ($[0] !== other || $[1] !== props.a.a.a || $[2] !== props.a.b) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a.a.a; - $[1] = props.a.b; - $[2] = other; + $[0] = other; + $[1] = props.a.a.a; + $[2] = props.a.b; $[3] = x; } else { x = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md index 37846215b1..fee31c9faf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md @@ -35,8 +35,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { @@ -65,19 +65,19 @@ function Component(props) { t0 = ; $[0] = props.p0; $[1] = props.p2; - $[2] = y; - $[3] = t0; + $[2] = t0; + $[3] = y; } else { - y = $[2]; - t0 = $[3]; + t0 = $[2]; + y = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md index 1be4143849..6290668015 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md @@ -30,8 +30,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { @@ -48,19 +48,19 @@ function Component(props) { $[0] = props.p0; $[1] = props.p2; $[2] = props.p3; - $[3] = y; - $[4] = t0; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md index 7480362a43..c6331bd4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md @@ -54,10 +54,10 @@ function Component(props) { } const c = t0; let t1; - if ($[3] !== c || $[4] !== a) { + if ($[3] !== a || $[4] !== c) { t1 = [c, a]; - $[3] = c; - $[4] = a; + $[3] = a; + $[4] = c; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md index 29e3e2757f..35637b1a59 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md @@ -41,10 +41,10 @@ function Component(props) { } const count = t1; let t2; - if ($[5] !== items || $[6] !== count) { + if ($[5] !== count || $[6] !== items) { t2 = { items, count }; - $[5] = items; - $[6] = count; + $[5] = count; + $[6] = items; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md index 3408f707d6..bb76df4de0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md @@ -40,10 +40,10 @@ function Component(props) { } const count = t1; let t2; - if ($[4] !== items || $[5] !== count) { + if ($[4] !== count || $[5] !== items) { t2 = { items, count }; - $[4] = items; - $[5] = count; + $[4] = count; + $[5] = items; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md index e682a01086..422f4cf547 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md @@ -42,8 +42,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(10); - let x; let t0; + let x; if ($[0] !== a) { x = []; if (a) { @@ -52,11 +52,11 @@ function foo(a, b, c) { t0 =
{x}
; $[0] = a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const y = t0; bb0: switch (b) { @@ -83,15 +83,15 @@ function foo(a, b, c) { } } let t1; - if ($[7] !== y || $[8] !== x) { + if ($[7] !== x || $[8] !== y) { t1 = (
{y} {x}
); - $[7] = y; - $[8] = x; + $[7] = x; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md index 3ad544344d..f3ddf57ec0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md @@ -34,7 +34,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md index 449aa6d3d8..5700634449 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md @@ -31,9 +31,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..18e9faf63b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== a.b.c || $[1] !== shouldReadA) { t1 = ( { @@ -50,8 +50,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a.b.c; + $[0] = a.b.c; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md index 701702f9dd..2dd9362404 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md @@ -80,10 +80,10 @@ function useFoo(t0) { x = $[6]; } let t1; - if ($[7] !== y || $[8] !== x.a.b) { + if ($[7] !== x.a.b || $[8] !== y) { t1 = [y, x.a.b]; - $[7] = y; - $[8] = x.a.b; + $[7] = x.a.b; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md index b4c25d5f45..e024bc893a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md index 359ba0dcde..055d1ce12e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; for (let i = 0; i < 5; i++) { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md index bb47587687..1c2162352b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md @@ -42,8 +42,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -77,11 +77,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md index 59225b5155..ca8228e2db 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md index 7a7f9a4b6e..ce12fb17b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md @@ -33,9 +33,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md index 2b925c2479..058bf85b69 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md @@ -39,8 +39,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { input, cond } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -61,11 +61,11 @@ function useFoo(t0) { } $[0] = cond; $[1] = input; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md index 291998c8ad..2715a0c92d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md @@ -40,7 +40,7 @@ function useFoo(t0) { const $ = _c(3); const { input, max } = t0; let x; - if ($[0] !== max || $[1] !== input.a.b) { + if ($[0] !== input.a.b || $[1] !== max) { x = []; let i = 0; while (true) { @@ -52,8 +52,8 @@ function useFoo(t0) { x.push(i); x.push(input.a.b); - $[0] = max; - $[1] = input.a.b; + $[0] = input.a.b; + $[1] = max; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md index 84b8fa1d43..845ea4b5ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md @@ -33,8 +33,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, hasAB, returnNull } = t0; - let x; let t1; + let x; if ($[0] !== hasAB || $[1] !== input.a || $[2] !== returnNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -68,11 +68,11 @@ function useFoo(t0) { $[0] = hasAB; $[1] = input.a; $[2] = returnNull; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md index a55922f610..d3e463495b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond2, cond1 } = t0; - let x; let t1; + let x; if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== input.a.b) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond1; $[1] = cond2; $[2] = input.a.b; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..d3a61a1019 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -36,14 +36,14 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== other || $[1] !== props.a) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; + $[0] = other; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md index 01ab4f6b0a..41a7a25d63 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md @@ -34,9 +34,9 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, inputHasAB, inputHasABC } = t0; - let x; let t1; - if ($[0] !== inputHasABC || $[1] !== input.a || $[2] !== inputHasAB) { + let x; + if ($[0] !== input.a || $[1] !== inputHasAB || $[2] !== inputHasABC) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -75,14 +75,14 @@ function useFoo(t0) { x.push(t2); } } - $[0] = inputHasABC; - $[1] = input.a; - $[2] = inputHasAB; - $[3] = x; - $[4] = t1; + $[0] = input.a; + $[1] = inputHasAB; + $[2] = inputHasABC; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md index a62223d72d..03e3e96397 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md @@ -40,14 +40,14 @@ import { identity } from "shared-runtime"; function useConditionalSubpath1(props, cond) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.b = props.a.b; if (identity(cond)) { x.a = props.a; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md index 02117feee2..5b246175f9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md @@ -48,14 +48,14 @@ function useConditionalSuperpath1(t0) { const $ = _c(3); const { props, cond } = t0; let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.a = props.a; if (identity(cond)) { x.b = props.a.b; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md index 34979e9de9..c91cf94445 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md @@ -49,15 +49,15 @@ function Component(t0) { const $ = _c(8); const { cond, other } = t0; let x; - if ($[0] !== other || $[1] !== cond) { + if ($[0] !== cond || $[1] !== other) { x = makeObject_Primitives(); setProperty(x, { b: 3, other }, "a"); identity(x.a.b); if (!cond) { x.a = null; } - $[0] = other; - $[1] = cond; + $[0] = cond; + $[1] = other; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md index da6912d35e..a2bd99e9a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md @@ -82,10 +82,10 @@ function Component(t0) { } const b = t3; let t4; - if ($[4] !== b || $[5] !== a) { + if ($[4] !== a || $[5] !== b) { t4 = { b, a }; - $[4] = b; - $[5] = a; + $[4] = a; + $[5] = b; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md index bba0b3f139..0089d20af2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md @@ -59,7 +59,7 @@ function Component(t0) { const serverTime = useServerTime(); let t1; let timestampLabel; - if ($[0] !== highlightedItem || $[1] !== serverTime || $[2] !== label) { + if ($[0] !== highlightedItem || $[1] !== label || $[2] !== serverTime) { const highlight = new Highlight(highlightedItem); const time = serverTime.get(); @@ -68,8 +68,8 @@ function Component(t0) { t1 = highlight.render(); $[0] = highlightedItem; - $[1] = serverTime; - $[2] = label; + $[1] = label; + $[2] = serverTime; $[3] = t1; $[4] = timestampLabel; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md index 9c21dc8400..ecdfa88b98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md @@ -66,10 +66,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md index 1b6e91a6fd..f9d725e0b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md @@ -65,10 +65,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md index 2dabc256f9..1ad6f6a047 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md @@ -71,10 +71,10 @@ function Foo() { const shouldCaptureObj = obj != null && CONST_TRUE; const t0 = shouldCaptureObj ? identity(obj) : null; let t1; - if ($[0] !== t0 || $[1] !== obj) { + if ($[0] !== obj || $[1] !== t0) { t1 = [t0, obj]; - $[0] = t0; - $[1] = obj; + $[0] = obj; + $[1] = t0; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md index 4921fd340b..d35cbe266f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md @@ -77,15 +77,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md index 80d046ec18..eda7a0cbfe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md @@ -74,15 +74,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md index 5eff1aa0fe..b5a7827728 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md @@ -21,13 +21,13 @@ function Component(listItem, thread) { let t0; let t1; let t2; - if ($[0] !== thread.threadType || $[1] !== listItem) { + if ($[0] !== listItem || $[1] !== thread.threadType) { const isFoo = isFooThread(thread.threadType); t1 = useBar; t2 = listItem; t0 = getBadgeText(listItem, isFoo); - $[0] = thread.threadType; - $[1] = listItem; + $[0] = listItem; + $[1] = thread.threadType; $[2] = t0; $[3] = t1; $[4] = t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md index 1d4a4b5d67..32cbbb2b91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md @@ -28,7 +28,7 @@ function V0(t0) { const { v1, v2 } = t0; const v5 = v1.v6?.v7; let t1; - if ($[0] !== v5 || $[1] !== v1 || $[2] !== v2) { + if ($[0] !== v1 || $[1] !== v2 || $[2] !== v5) { t1 = ( {v5 != null ? ( @@ -40,9 +40,9 @@ function V0(t0) { )} ); - $[0] = v5; - $[1] = v1; - $[2] = v2; + $[0] = v1; + $[1] = v2; + $[2] = v5; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md index cec64e8cf0..ab409b366d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md @@ -36,7 +36,7 @@ function useTest(t0) { const $ = _c(3); const { isNull, data } = t0; let t1; - if ($[0] !== isNull || $[1] !== data) { + if ($[0] !== data || $[1] !== isNull) { t1 = Builder.makeBuilder(isNull, "hello world") ?.push("1", 2) ?.push(3, { a: 4, b: 5, c: data }) @@ -47,8 +47,8 @@ function useTest(t0) { ) ?.push(7, "8") ?.push("8", Builder.makeBuilder(!isNull)?.push(9).vals)?.vals; - $[0] = isNull; - $[1] = data; + $[0] = data; + $[1] = isNull; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md index 73674e5fff..496d80d52b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md @@ -77,10 +77,10 @@ function Component(t0) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== result) { + if ($[4] !== result || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = result; + $[4] = result; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md index 2e9daceed7..1b49552bea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md @@ -26,8 +26,8 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); const item = props.item; - let t0; let baseVideos; + let t0; let thumbnails; if ($[0] !== item) { thumbnails = []; @@ -40,12 +40,12 @@ function Component(props) { } }); $[0] = item; - $[1] = t0; - $[2] = baseVideos; + $[1] = baseVideos; + $[2] = t0; $[3] = thumbnails; } else { - t0 = $[1]; - baseVideos = $[2]; + baseVideos = $[1]; + t0 = $[2]; thumbnails = $[3]; } t0 = undefined; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md index 9aa75b2162..aece9665c9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const [bar] = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md index ded1eb4006..3681e9c469 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const bar = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md index f0a46be168..3e66b20041 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const { bar } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md index 7c9c6a5028..1201a9a737 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md @@ -73,14 +73,14 @@ function foo(props) { } const header = t0; let y; - if ($[5] !== x || $[6] !== props.b || $[7] !== props.c) { + if ($[5] !== props.b || $[6] !== props.c || $[7] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[5] = x; - $[6] = props.b; - $[7] = props.c; + $[5] = props.b; + $[6] = props.c; + $[7] = x; $[8] = y; $[9] = x; } else { @@ -103,15 +103,15 @@ function foo(props) { } const content = t1; let t2; - if ($[13] !== header || $[14] !== content) { + if ($[13] !== content || $[14] !== header) { t2 = ( <> {header} {content} ); - $[13] = header; - $[14] = content; + $[13] = content; + $[14] = header; $[15] = t2; } else { t2 = $[15]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md index 36b1d4541a..143496678e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md @@ -53,30 +53,30 @@ import { c as _c } from "react/compiler-runtime"; // note: comments are for the // emitted function foo(props) { const $ = _c(14); - let x; let t0; + let x; if ($[0] !== props.a) { x = []; x.push(props.a); t0 =
{x}
; $[0] = props.a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const header = t0; let y; - if ($[3] !== x || $[4] !== props.b || $[5] !== props.c) { + if ($[3] !== props.b || $[4] !== props.c || $[5] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[3] = x; - $[4] = props.b; - $[5] = props.c; + $[3] = props.b; + $[4] = props.c; + $[5] = x; $[6] = y; $[7] = x; } else { @@ -99,15 +99,15 @@ function foo(props) { } const content = t1; let t2; - if ($[11] !== header || $[12] !== content) { + if ($[11] !== content || $[12] !== header) { t2 = ( <> {header} {content} ); - $[11] = header; - $[12] = content; + $[11] = content; + $[12] = header; $[13] = t2; } else { t2 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md index 5f10f44202..36a68d07c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md @@ -43,9 +43,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); - let text; let t0; let t1; + let text; if ($[0] !== statusName) { const { status, text: t2 } = foo(statusName); text = t2; @@ -54,13 +54,13 @@ function Component(statusName) { t1 = identity(bg); t0 = identity(color); $[0] = statusName; - $[1] = text; - $[2] = t0; - $[3] = t1; + $[1] = t0; + $[2] = t1; + $[3] = text; } else { - text = $[1]; - t0 = $[2]; - t1 = $[3]; + t0 = $[1]; + t1 = $[2]; + text = $[3]; } let t2; if ($[4] !== text) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md index e2cd53bd0d..d8e991dc46 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md @@ -46,9 +46,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); + let font; let t0; let text; - let font; if ($[0] !== statusName) { const { status, text: t1 } = foo(statusName); text = t1; @@ -58,13 +58,13 @@ function Component(statusName) { t0 = identity(color); $[0] = statusName; - $[1] = t0; - $[2] = text; - $[3] = font; + $[1] = font; + $[2] = t0; + $[3] = text; } else { - t0 = $[1]; - text = $[2]; - font = $[3]; + font = $[1]; + t0 = $[2]; + text = $[3]; } const bg = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..788109636b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -34,8 +34,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); - let y; let t0; + let y; if ($[0] !== props) { const x = []; bb0: switch (props.p0) { @@ -63,19 +63,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[4] !== child || $[5] !== y) { t1 = {child}; - $[4] = y; - $[5] = child; + $[4] = child; + $[5] = y; $[6] = t1; } else { t1 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..2628982655 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -29,8 +29,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(6); - let y; let t0; + let y; if ($[0] !== props) { const x = []; switch (props.p0) { @@ -45,19 +45,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[3] !== child || $[4] !== y) { t1 = {child}; - $[3] = y; - $[4] = child; + $[3] = child; + $[4] = y; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md index f106382d64..4864c51a1f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -64,8 +64,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -85,15 +85,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md index 77fd38aea1..c661094fdd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md @@ -55,7 +55,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -70,8 +70,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,15 +91,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md index d064a48b71..7ac6486b47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md @@ -18,10 +18,10 @@ function App() { const $ = _c(3); const [foo, bar] = useContext(MyContext); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md index f82af06866..3eac66304b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md @@ -22,10 +22,10 @@ function App() { const { foo } = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md index 573b6db231..4cca8b19d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md @@ -22,10 +22,10 @@ function App() { const [foo] = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md index 03ce7f97ba..f5a3916626 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md @@ -22,10 +22,10 @@ function App() { const { joe: t0, bar } = useContext(MyContext); const { foo } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md index 55387503cf..0888d67b2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md @@ -22,10 +22,10 @@ function App() { const foo = context.foo; const bar = context.bar; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md index 268fa8d7eb..2c27360c9f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md @@ -42,9 +42,9 @@ import { mutate, setProperty, throwErrorWithMessageIf } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { value, cond } = t0; - let y; let t1; - if ($[0] !== value || $[1] !== cond) { + let y; + if ($[0] !== cond || $[1] !== value) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { y = [value]; @@ -68,13 +68,13 @@ function useFoo(t0) { } y.push(x); } - $[0] = value; - $[1] = cond; - $[2] = y; - $[3] = t1; + $[0] = cond; + $[1] = value; + $[2] = t1; + $[3] = y; } else { - y = $[2]; - t1 = $[3]; + t1 = $[2]; + y = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md index b04bd53458..562c0bc1c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md @@ -32,8 +32,8 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -47,11 +47,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md index af5f2ebfcf..71a59aba2f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md @@ -33,8 +33,8 @@ const { shallowCopy, throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -52,11 +52,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md index 54d5be2d6b..c3c45beb86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md @@ -78,10 +78,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -95,10 +95,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md index 072c6d03d9..4acbd2dfdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md @@ -76,10 +76,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -93,10 +93,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md index caa74267f3..5016b0c4df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md index a92abd4ca5..3f341fc665 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md index fbd13dc1b0..7fa86838e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md @@ -38,22 +38,22 @@ function component(a) { const f = typeof t.t; let t0; if ( - $[0] !== z || - $[1] !== p || - $[2] !== q || + $[0] !== e || + $[1] !== f || + $[2] !== m || $[3] !== n || - $[4] !== m || - $[5] !== e || - $[6] !== f + $[4] !== p || + $[5] !== q || + $[6] !== z ) { t0 = { z, p, q, n, m, e, f }; - $[0] = z; - $[1] = p; - $[2] = q; + $[0] = e; + $[1] = f; + $[2] = m; $[3] = n; - $[4] = m; - $[5] = e; - $[6] = f; + $[4] = p; + $[5] = q; + $[6] = z; $[7] = t0; } else { t0 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md index 663a6788f3..7f79cae4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md @@ -89,10 +89,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..d94a5e7e37 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -109,10 +109,10 @@ function Inner(props) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== output) { + if ($[4] !== output || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = output; + $[4] = output; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md index 99effce934..cf0093ea94 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md @@ -91,10 +91,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md index 1292ea1489..c3e115fa0d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md @@ -51,7 +51,7 @@ function Component(props) { } const exit = t0; let t1; - if ($[2] !== item.value || $[3] !== exit) { + if ($[2] !== exit || $[3] !== item.value) { t1 = () => { const cleanup = GlobalEventEmitter.addListener("onInput", () => { if (item.value) { @@ -60,8 +60,8 @@ function Component(props) { }); return () => cleanup.remove(); }; - $[2] = item.value; - $[3] = exit; + $[2] = exit; + $[3] = item.value; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md index b6a4187355..28d3e33359 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md @@ -62,13 +62,13 @@ function Component(props) { {w} ); - let condition = $[2] !== x || $[3] !== w; + let condition = $[2] !== w || $[3] !== x; if (!condition) { let old$t1 = $[4]; $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(7:10)"); } - $[2] = x; - $[3] = w; + $[2] = w; + $[3] = x; $[4] = t1; if (condition) { t1 = ( From fde153bb90411853fa53206b595db5970a464191 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 25 Oct 2024 11:36:53 -0700 Subject: [PATCH 054/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. This stack was tested by syncing internally to Meta and spot checking ~40 files for differences in compilation output. Similar to snap fixtures, we see more granular memo dependencies and more outlining. ([meta internal link](https://www.internalfb.com/intern/paste/P1667309207/)) ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 2 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 61 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../src/SSA/EliminateRedundantPhi.ts | 19 +++ .../src/SSA/EnterSSA.ts | 3 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 38 files changed, 129 insertions(+), 336 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..4c57e792e3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,8 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..c030a90cea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,23 +58,7 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } case 'LoadLocal': @@ -115,11 +84,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +96,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +113,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +140,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..37394daa8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -13,6 +13,8 @@ import { eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +143,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 19506bc5d8ec02214f2fa40a1283265d9e838ea2 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 055/422] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR ' --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1869 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 3e2b5597ac..ce38e91af4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,16 +231,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..352552bf02 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 7ac7b52f6c1500da5c5b7cbccc95a0cac9ce86ec Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 056/422] [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 3c9bf70e6d47c3fea8fb43bd0b916d654b49f941 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 057/422] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200 Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From e288deabd71d00290ae675452f0e1d62aa2de6e9 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 058/422] [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 a19e4bb85c7322198be35c04b5f0cb1451565e24 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 059/422] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index ce38e91af4..fab2111735 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,6 +231,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 02e60eff91..ed56ff0681 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 157e2de81a..bb99a5d90f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From ba32a1621f76c270495ae690195c6a0bae0d8673 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 060/422] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 9494436d1f..ecc22365dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From bb3706c3ae6704286323ad7cbc9599406b1fceeb Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 061/422] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From 52a7bfaa332181e6c02bcff88cb33d0146eec122 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 062/422] [compiler][be] Clean up nested function context in DCE Now that we rely on function context exclusively, let's clean up `HIRFunction.context` after DCE. This PR is in preparation of #31204, which would otherwise have unnecessary declarations (of context values that become entirely DCE'd) ' --- .../src/Optimization/DeadCodeElimination.ts | 8 ++++ .../compiler/arrow-expr-directive.expect.md | 5 ++- .../compiler/capture-param-mutate.expect.md | 9 ++-- .../function-expr-directive.expect.md | 5 ++- .../compiler/merge-scopes-callback.expect.md | 5 ++- ...reactive-scope-with-early-return.expect.md | 42 ++++++++----------- ...react-hooks-based-on-import-name.expect.md | 5 ++- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 885ec2b3ab..0202d3ecf0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103..93eb2bd28a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c..9e4709616d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md index 3980434bde..8c4aa612e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md @@ -34,7 +34,7 @@ function Component() { t0 = function update() { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -51,6 +51,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md index edf748de5c..0ff9773f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md @@ -32,7 +32,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -61,6 +61,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index 0c1bf1cd70..506e4ca713 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -39,7 +39,7 @@ function Component() { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions function Component() { - const $ = _c(8); + const $ = _c(7); const items = useItems(); let t0; let t1; @@ -47,35 +47,25 @@ function Component() { if ($[0] !== items) { t2 = Symbol.for("react.early_return_sentinel"); bb0: { - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = (t4) => { - const [item] = t4; - return item.name != null; - }; - $[4] = t3; - } else { - t3 = $[4]; - } - t0 = items.filter(t3); + t0 = items.filter(_temp); const filteredItems = t0; if (filteredItems.length === 0) { - let t4; - if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t4 = ( + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (
); - $[5] = t4; + $[4] = t3; } else { - t4 = $[5]; + t3 = $[4]; } - t2 = t4; + t2 = t3; break bb0; } - t1 = filteredItems.map(_temp); + t1 = filteredItems.map(_temp2); } $[0] = items; $[1] = t1; @@ -90,19 +80,23 @@ function Component() { return t2; } let t3; - if ($[6] !== t1) { + if ($[5] !== t1) { t3 = <>{t1}; - $[6] = t1; - $[7] = t3; + $[5] = t1; + $[6] = t3; } else { - t3 = $[7]; + t3 = $[6]; } return t3; } -function _temp(t0) { +function _temp2(t0) { const [item_0] = t0; return ; } +function _temp(t0) { + const [item] = t0; + return item.name != null; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md index dc3081321e..496d61df9d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md @@ -38,7 +38,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -67,6 +67,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, From 77fefaa6282e840f654e11ae1115e08924d21ffc Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 063/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../src/SSA/EliminateRedundantPhi.ts | 19 +++ .../src/SSA/EnterSSA.ts | 3 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 38 files changed, 130 insertions(+), 335 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index ecc22365dd..41670b1e81 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index fab2111735..f529e2cefe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -232,7 +232,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 954fb6f400..2f5c387645 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..37394daa8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -13,6 +13,8 @@ import { eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +143,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From a5d62b00623d123b517a0f03f6200aeda9a187e7 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:06:55 -0500 Subject: [PATCH 064/422] [compiler][be] Stabilize compiler output: sort deps and decls by name All dependencies and declarations of a reactive scope can be reordered to scope start/end. i.e. generated code does not depend on conditional short-circuiting logic as dependencies are inferred to have no side effects. Sorting these by name helps us get higher signal compilation snapshot diffs when upgrading the compiler and testing PRs internally at Meta --- .../ReactiveScopes/CodegenReactiveFunction.ts | 51 ++++++++++++++++++- .../compiler/array-at-effect.expect.md | 6 +-- .../compiler/array-property-call.expect.md | 10 ++-- .../bug-invalid-phi-as-dependency.expect.md | 6 +-- ...fun-alias-captured-mutate-2-iife.expect.md | 6 +-- ...ring-fun-alias-captured-mutate-2.expect.md | 6 +-- ...alias-captured-mutate-arr-2-iife.expect.md | 6 +-- ...-fun-alias-captured-mutate-arr-2.expect.md | 6 +-- ...c-alias-captured-mutate-arr-iife.expect.md | 6 +-- ...g-func-alias-captured-mutate-arr.expect.md | 6 +-- ...-func-alias-captured-mutate-iife.expect.md | 6 +-- ...uring-func-alias-captured-mutate.expect.md | 6 +-- ...turing-function-member-expr-call.expect.md | 6 +-- .../fixtures/compiler/component.expect.md | 12 ++--- .../compiler/dependencies-outputs.expect.md | 6 +-- .../destructure-in-branch-ssa.expect.md | 8 +-- ...g-same-property-identifier-names.expect.md | 6 +-- .../fixtures/compiler/destructuring.expect.md | 34 ++++++------- ...er-declaration-of-previous-scope.expect.md | 10 ++-- .../existing-variables-with-c-name.expect.md | 6 +-- .../compiler/fast-refresh-reloading.expect.md | 6 +-- ...-mutable-range-destructured-prop.expect.md | 6 +-- ...btparam-with-jsx-element-content.expect.md | 6 +-- ...onmutating-loop-local-collection.expect.md | 6 +-- ...pression-prototype-call-mutating.expect.md | 6 +-- ...unctionexpr-conditional-access-2.expect.md | 6 +-- .../functionexpr–conditional-access.expect.md | 6 +-- ...bals-dont-resolve-local-useState.expect.md | 6 +-- .../fixtures/compiler/hook-noAlias.expect.md | 6 +-- .../compiler/hooks-with-prefix.expect.md | 6 +-- ...incompatible-destructuring-kinds.expect.md | 14 ++--- ...-promoted-to-outer-scope-dynamic.expect.md | 20 ++++---- ...jsx-outlining-child-stored-in-id.expect.md | 12 ++--- .../jsx-outlining-jsx-stored-in-id.expect.md | 18 +++---- .../jsx-outlining-separate-nested.expect.md | 22 ++++---- .../compiler/jsx-outlining-simple.expect.md | 18 +++---- ...-tag-evaluation-order-non-global.expect.md | 10 ++-- .../lambda-capture-returned-alias.expect.md | 6 +-- .../lower-context-acess-multiple.expect.md | 6 +-- .../lower-context-selector-simple.expect.md | 6 +-- ...ge-consecutive-scopes-reordering.expect.md | 6 +-- .../compiler/method-call-computed.expect.md | 8 +-- .../compiler/method-call-fn-call.expect.md | 8 +-- .../fixtures/compiler/method-call.expect.md | 6 +-- ...pture-in-unsplittable-memo-block.expect.md | 10 ++-- .../object-shorthand-method-nested.expect.md | 6 +-- ...al-member-expression-as-memo-dep.expect.md | 6 +-- ...ession-single-with-unconditional.expect.md | 6 +-- ...ptional-member-expression-single.expect.md | 6 +-- ...ession-with-conditional-optional.expect.md | 12 ++--- ...mber-expression-with-conditional.expect.md | 12 ++--- ...rly-return-within-reactive-scope.expect.md | 10 ++-- ...Callback-in-other-reactive-block.expect.md | 8 +-- ...k-reordering-deplist-controlflow.expect.md | 16 +++--- ...useMemo-conditional-access-alloc.expect.md | 6 +-- ...eMemo-conditional-access-noAlloc.expect.md | 6 +-- .../useMemo-in-other-reactive-block.expect.md | 8 +-- ...-reordering-depslist-controlflow.expect.md | 6 +-- ...signed-loop-force-scopes-enabled.expect.md | 8 +-- ...al-member-expression-as-memo-dep.expect.md | 6 +-- ...ession-single-with-unconditional.expect.md | 6 +-- ...ptional-member-expression-single.expect.md | 6 +-- ...rly-return-within-reactive-scope.expect.md | 10 ++-- ...r-function-cond-access-local-var.expect.md | 6 +-- ...function-cond-access-not-hoisted.expect.md | 6 +-- ...n-uncond-access-hoists-other-dep.expect.md | 6 +-- ...uncond-optional-hoists-other-dep.expect.md | 12 ++--- .../promote-uncond.expect.md | 8 +-- .../switch-non-final-default.expect.md | 16 +++--- .../switch.expect.md | 16 +++--- ...-analysis-interleaved-reactivity.expect.md | 6 +-- ...ve-via-mutation-of-computed-load.expect.md | 6 +-- ...ve-via-mutation-of-property-load.expect.md | 6 +-- .../reassignment-separate-scopes.expect.md | 16 +++--- ...eactive-cond-deps-break-in-scope.expect.md | 6 +-- ...active-cond-deps-return-in-scope.expect.md | 16 +++--- ...function-cond-access-not-hoisted.expect.md | 6 +-- .../hoist-deps-diff-ssa-instance.expect.md | 6 +-- .../jump-poisoned/break-in-scope.expect.md | 6 +-- .../loop-break-in-scope.expect.md | 6 +-- ...e-if-nonexhaustive-poisoned-deps.expect.md | 10 ++-- ...-if-nonexhaustive-poisoned-deps1.expect.md | 10 ++-- .../jump-poisoned/return-in-scope.expect.md | 16 +++--- .../return-poisons-outer-scope.expect.md | 10 ++-- ...p-target-within-scope-loop-break.expect.md | 6 +-- ...e-if-exhaustive-nonpoisoned-deps.expect.md | 10 ++-- ...-if-exhaustive-nonpoisoned-deps1.expect.md | 10 ++-- .../promote-uncond.expect.md | 6 +-- ...duce-if-exhaustive-poisoned-deps.expect.md | 18 +++---- .../subpath-order1.expect.md | 6 +-- .../superpath-order1.expect.md | 6 +-- .../uncond-access-in-mutable-range.expect.md | 6 +-- .../reordering-across-blocks.expect.md | 6 +-- ...ed-property-load-for-method-call.expect.md | 6 +-- ...uned-scope-leaks-value-via-alias.expect.md | 6 +-- ...invalid-pruned-scope-leaks-value.expect.md | 6 +-- ...o-invalid-reactivity-value-block.expect.md | 6 +-- ...lack-of-phi-types-explicit-types.expect.md | 6 +-- ...ng-memoization-lack-of-phi-types.expect.md | 6 +-- .../repro-no-value-for-temporary.expect.md | 6 +-- ...ro-propagate-type-of-ternary-jsx.expect.md | 8 +-- ...epro-slow-validate-preserve-memo.expect.md | 6 +-- ...ble-code-early-return-in-useMemo.expect.md | 6 +-- .../fixtures/compiler/repro.expect.md | 10 ++-- .../rest-param-with-array-pattern.expect.md | 6 +-- .../rest-param-with-identifier.expect.md | 6 +-- ...param-with-object-spread-pattern.expect.md | 6 +-- ...s-dep-and-redeclare-maybe-frozen.expect.md | 14 ++--- ...me-variable-as-dep-and-redeclare.expect.md | 24 ++++----- ...assignment-to-scope-declarations.expect.md | 14 ++--- ...ixed-local-and-scope-declaration.expect.md | 14 ++--- .../switch-non-final-default.expect.md | 16 +++--- .../fixtures/compiler/switch.expect.md | 16 +++--- .../todo.jsx-outlining-children.expect.md | 12 ++--- ...odo.jsx-outlining-duplicate-prop.expect.md | 12 ++--- ...ntext-access-array-destructuring.expect.md | 6 +-- ...text-access-destructure-multiple.expect.md | 6 +-- ...r-context-access-mixed-array-obj.expect.md | 6 +-- ...text-access-nested-destructuring.expect.md | 6 +-- ...wer-context-access-property-load.expect.md | 6 +-- .../try-catch-in-nested-scope.expect.md | 16 +++--- .../try-catch-with-catch-param.expect.md | 10 ++-- .../compiler/try-catch-with-return.expect.md | 10 ++-- ...type-provider-log-default-import.expect.md | 12 ++--- .../compiler/type-provider-log.expect.md | 12 ++--- ...r-store-capture-namespace-import.expect.md | 20 ++++---- .../type-provider-store-capture.expect.md | 20 ++++---- .../fixtures/compiler/unary-expr.expect.md | 24 ++++----- .../use-operator-call-expression.expect.md | 6 +-- .../use-operator-conditional.expect.md | 6 +-- .../use-operator-method-call.expect.md | 6 +-- .../useEffect-nested-lambdas.expect.md | 6 +-- .../useState-unpruned-dependency.expect.md | 6 +-- 133 files changed, 648 insertions(+), 601 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 297c771254..167db6dede 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -34,6 +34,7 @@ import { ReactiveInstruction, ReactiveScope, ReactiveScopeBlock, + ReactiveScopeDeclaration, ReactiveScopeDependency, ReactiveTerminal, ReactiveValue, @@ -572,7 +573,8 @@ function codegenReactiveScope( const changeExpressions: Array = []; const changeExpressionComments: Array = []; const outputComments: Array = []; - for (const dep of scope.dependencies) { + + for (const dep of [...scope.dependencies].sort(compareScopeDependency)) { const index = cx.nextCacheIndex; changeExpressionComments.push(printDependencyComment(dep)); const comparison = t.binaryExpression( @@ -615,7 +617,10 @@ function codegenReactiveScope( ); } let firstOutputIndex: number | null = null; - for (const [, {identifier}] of scope.declarations) { + + for (const [, {identifier}] of [...scope.declarations].sort(([, a], [, b]) => + compareScopeDeclaration(a, b), + )) { const index = cx.nextCacheIndex; if (firstOutputIndex === null) { firstOutputIndex = index; @@ -2566,3 +2571,45 @@ function convertIdentifier(identifier: Identifier): t.Identifier { ); return t.identifier(identifier.name.value); } + +function compareScopeDependency( + a: ReactiveScopeDependency, + b: ReactiveScopeDependency, +): number { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected named identifier for dependency', + loc: a.identifier.loc, + }, + ); + const aName = [ + a.identifier.name.value, + ...a.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`), + ].join('.'); + const bName = [ + b.identifier.name.value, + ...b.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`), + ].join('.'); + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; +} + +function compareScopeDeclaration( + a: ReactiveScopeDeclaration, + b: ReactiveScopeDeclaration, +): number { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected named identifier for declaration', + loc: a.identifier.loc, + }, + ); + const aName = a.identifier.name.value; + const bName = b.identifier.name.value; + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md index 3aa51ba6d6..a8bad51215 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md @@ -41,7 +41,7 @@ function ArrayAtTest(props) { } const arr = t1; let t2; - if ($[4] !== props.y || $[5] !== arr) { + if ($[4] !== arr || $[5] !== props.y) { let t3; if ($[7] !== props.y) { t3 = bar(props.y); @@ -51,8 +51,8 @@ function ArrayAtTest(props) { t3 = $[8]; } t2 = arr.at(t3); - $[4] = props.y; - $[5] = arr; + $[4] = arr; + $[5] = props.y; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md index 7aa47c5803..6618be4a6c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md @@ -24,18 +24,18 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(11); - let t0; let a; + let t0; if ($[0] !== props.a || $[1] !== props.b) { a = [props.a, props.b, "hello"]; t0 = a.push(42); $[0] = props.a; $[1] = props.b; - $[2] = t0; - $[3] = a; + $[2] = a; + $[3] = t0; } else { - t0 = $[2]; - a = $[3]; + a = $[2]; + t0 = $[3]; } const x = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md index 32e2c9fd64..09d2d8800b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md @@ -70,10 +70,10 @@ function Component() { throw new Error("invariant broken"); } let t1; - if ($[1] !== obj || $[2] !== boxedInner) { + if ($[1] !== boxedInner || $[2] !== obj) { t1 = ; - $[1] = obj; - $[2] = boxedInner; + $[1] = boxedInner; + $[2] = obj; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md index 65ab9c277c..5e0b32709b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md @@ -32,7 +32,7 @@ import { mutate } from "shared-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md index 170f68bade..f9ce3f2e98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md index e315cd401d..81737a1ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md index 7371f3d27a..38590d1559 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md index cc41adcbbc..4882aa822f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md index 34f6a55740..7c94c33e49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md index b2d7048756..60493dd25a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..14532562fb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md index 51679299fe..cab9c9a500 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md @@ -46,10 +46,10 @@ function component(t0) { } const hide = t2; let t3; - if ($[4] !== poke || $[5] !== hide) { + if ($[4] !== hide || $[5] !== poke) { t3 = ; - $[4] = poke; - $[5] = hide; + $[4] = hide; + $[5] = poke; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md index 80d6e6df8c..c6037fd2bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md @@ -46,7 +46,7 @@ function Component(props) { const items = props.items; const maxItems = props.maxItems; let renderedItems; - if ($[0] !== maxItems || $[1] !== items) { + if ($[0] !== items || $[1] !== maxItems) { renderedItems = []; const seen = new Set(); const max = Math.max(0, maxItems); @@ -62,8 +62,8 @@ function Component(props) { break; } } - $[0] = maxItems; - $[1] = items; + $[0] = items; + $[1] = maxItems; $[2] = renderedItems; } else { renderedItems = $[2]; @@ -79,15 +79,15 @@ function Component(props) { t0 = $[4]; } let t1; - if ($[5] !== t0 || $[6] !== renderedItems) { + if ($[5] !== renderedItems || $[6] !== t0) { t1 = (
{t0} {renderedItems}
); - $[5] = t0; - $[6] = renderedItems; + $[5] = renderedItems; + $[6] = t0; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md index 6fc686cb19..f0f9911c07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md @@ -41,7 +41,7 @@ function foo(a, b) { x = $[1]; } let y; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { y = []; if (x.length) { y.push(x); @@ -49,8 +49,8 @@ function foo(a, b) { if (b) { y.push(b); } - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = y; } else { y = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md index d65082cbc8..b159789106 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md @@ -61,11 +61,11 @@ function useFoo(props) { z = $[4]; } let t0; - if ($[5] !== x || $[6] !== y || $[7] !== myList) { + if ($[5] !== myList || $[6] !== x || $[7] !== y) { t0 = { x, y, myList }; - $[5] = x; - $[6] = y; - $[7] = myList; + $[5] = myList; + $[6] = x; + $[7] = y; $[8] = t0; } else { t0 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md index b86498b922..3e1c4771ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md @@ -41,10 +41,10 @@ function Component(props) { } const sameName = t1; let t2; - if ($[2] !== sameName || $[3] !== renamed) { + if ($[2] !== renamed || $[3] !== sameName) { t2 = [sameName, renamed]; - $[2] = sameName; - $[3] = renamed; + $[2] = renamed; + $[3] = sameName; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md index c175cc558c..f292e83e16 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md @@ -36,45 +36,45 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(18); - let t0; let d; let h; + let t0; if ($[0] !== a) { [d, t0, ...h] = a; $[0] = a; - $[1] = t0; - $[2] = d; - $[3] = h; + $[1] = d; + $[2] = h; + $[3] = t0; } else { - t0 = $[1]; - d = $[2]; - h = $[3]; + d = $[1]; + h = $[2]; + t0 = $[3]; } const [t1] = t0; - let t2; let g; + let t2; if ($[4] !== t1) { ({ e: t2, ...g } = t1); $[4] = t1; - $[5] = t2; - $[6] = g; + $[5] = g; + $[6] = t2; } else { - t2 = $[5]; - g = $[6]; + g = $[5]; + t2 = $[6]; } const { f } = t2; const { l: t3, p } = b; const { m: t4 } = t3; - let t5; let o; + let t5; if ($[7] !== t4) { [t5, ...o] = t4; $[7] = t4; - $[8] = t5; - $[9] = o; + $[8] = o; + $[9] = t5; } else { - t5 = $[8]; - o = $[9]; + o = $[8]; + t5 = $[9]; } const [n] = t5; let t6; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md index 29780eb76c..ce5bfda644 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md @@ -53,8 +53,8 @@ import { ValidateMemoization } from "shared-runtime"; function Component(t0) { const $ = _c(25); const { a, b, c } = t0; - let y; let x; + let y; if ($[0] !== a || $[1] !== b || $[2] !== c) { x = []; if (a) { @@ -73,11 +73,11 @@ function Component(t0) { $[0] = a; $[1] = b; $[2] = c; - $[3] = y; - $[4] = x; + $[3] = x; + $[4] = y; } else { - y = $[3]; - x = $[4]; + x = $[3]; + y = $[4]; } let t1; if ($[7] !== y) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md index 0d671a3de2..5cde3bde23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md @@ -61,10 +61,10 @@ function Component(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== array) { + if ($[4] !== array || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = array; + $[4] = array; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md index ecd03a0b10..4175d23fda 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md @@ -59,10 +59,10 @@ function Component(props) { t3 = $[4]; } let t4; - if ($[5] !== t3 || $[6] !== doubled) { + if ($[5] !== doubled || $[6] !== t3) { t4 = ; - $[5] = t3; - $[6] = doubled; + $[5] = doubled; + $[6] = t3; $[7] = t4; } else { t4 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md index d56f7a98ad..9bb651aa67 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md @@ -61,10 +61,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== el) { + if ($[4] !== el || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = el; + $[4] = el; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md index d58c25b510..56ffb70cb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md @@ -32,7 +32,7 @@ function Component(t0) { const $ = _c(4); const { name, data, icon } = t0; let t1; - if ($[0] !== name || $[1] !== icon || $[2] !== data) { + if ($[0] !== data || $[1] !== icon || $[2] !== name) { t1 = ( {fbt._( @@ -61,9 +61,9 @@ function Component(t0) { )} ); - $[0] = name; + $[0] = data; $[1] = icon; - $[2] = data; + $[2] = name; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md index cff8c5bd13..4abe630044 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md @@ -91,10 +91,10 @@ function Component(t0) { t5 = $[9]; } let t6; - if ($[10] !== x || $[11] !== b) { + if ($[10] !== b || $[11] !== x) { t6 = [x, b]; - $[10] = x; - $[11] = b; + $[10] = b; + $[11] = x; $[12] = t6; } else { t6 = $[12]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md index be59673c15..9888e96222 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md @@ -59,10 +59,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== a_0) { + if ($[4] !== a_0 || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = a_0; + $[4] = a_0; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..32498e1379 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -36,10 +36,10 @@ function Component(t0) { } const f = t1; let t2; - if ($[2] !== props || $[3] !== f) { + if ($[2] !== f || $[3] !== props) { t2 = props == null ? _temp : f; - $[2] = props; - $[3] = f; + $[2] = f; + $[3] = props; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..4a62bf6f24 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -36,10 +36,10 @@ function Component(props) { } const getLength = t0; let t1; - if ($[2] !== props.bar || $[3] !== getLength) { + if ($[2] !== getLength || $[3] !== props.bar) { t1 = props.bar && getLength(); - $[2] = props.bar; - $[3] = getLength; + $[2] = getLength; + $[3] = props.bar; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md index 7548a3b639..be7f3f1bd2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md @@ -56,10 +56,10 @@ function Component() { t0 = $[1]; } let t1; - if ($[2] !== t0 || $[3] !== state) { + if ($[2] !== state || $[3] !== t0) { t1 =
{state}
; - $[2] = t0; - $[3] = state; + $[2] = state; + $[3] = t0; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md index 96ccd1e2f1..329d57a035 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md @@ -41,10 +41,10 @@ function Component(props) { console.log(props); }, [props.a]); let t1; - if ($[2] !== x || $[3] !== item) { + if ($[2] !== item || $[3] !== x) { t1 = [x, item]; - $[2] = x; - $[3] = item; + $[2] = item; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md index f7e02a53f1..085df625f5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md @@ -73,15 +73,15 @@ function Component() { t1 = $[4]; } let t3; - if ($[5] !== t2 || $[6] !== json) { + if ($[5] !== json || $[6] !== t2) { t3 = (
{t2} {json}
); - $[5] = t2; - $[6] = json; + $[5] = json; + $[6] = t2; $[7] = t3; } else { t3 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md index 970cc50f03..8afc59a80b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md @@ -29,22 +29,22 @@ import { Stringify } from "shared-runtime"; function Component(t0) { const $ = _c(4); - let t1; let a; let b; + let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { a = "a"; const [t2, t3] = [null, null]; t1 = t3; a = t2; - $[0] = t1; - $[1] = a; - $[2] = b; + $[0] = a; + $[1] = b; + $[2] = t1; } else { - t1 = $[0]; - a = $[1]; - b = $[2]; + a = $[0]; + b = $[1]; + t1 = $[2]; } b = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md index becc4066e7..735462657f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md @@ -27,10 +27,10 @@ function Component(props) { const $ = _c(15); const item = useFragment(FRAGMENT, props.item); useFreeze(item); - let t0; let T0; - let t1; let T1; + let t0; + let t1; if ($[0] !== item) { const count = new MaybeMutable(item); @@ -44,15 +44,15 @@ function Component(props) { } t0 = maybeMutate(count); $[0] = item; - $[1] = t0; - $[2] = T0; - $[3] = t1; - $[4] = T1; + $[1] = T0; + $[2] = T1; + $[3] = t0; + $[4] = t1; } else { - t0 = $[1]; - T0 = $[2]; - t1 = $[3]; - T1 = $[4]; + T0 = $[1]; + T1 = $[2]; + t0 = $[3]; + t1 = $[4]; } let t2; if ($[6] !== t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..b84229156b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -83,10 +83,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -98,15 +98,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md index 496282e1ef..7fca963134 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md @@ -52,7 +52,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -66,8 +66,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -94,10 +94,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -109,15 +109,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md index 7f86546cd4..9d2b254c06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md @@ -60,7 +60,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -73,8 +73,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -117,7 +117,7 @@ function _temp(t0) { t3 = $[5]; } let t4; - if ($[6] !== x || $[7] !== t1 || $[8] !== t2 || $[9] !== t3) { + if ($[6] !== t1 || $[7] !== t2 || $[8] !== t3 || $[9] !== x) { t4 = ( {t1} @@ -125,10 +125,10 @@ function _temp(t0) { {t3} ); - $[6] = x; - $[7] = t1; - $[8] = t2; - $[9] = t3; + $[6] = t1; + $[7] = t2; + $[8] = t3; + $[9] = x; $[10] = t4; } else { t4 = $[10]; @@ -140,15 +140,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md index 9e268227a2..09323f5ac6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -63,8 +63,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,10 +91,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -106,15 +106,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md index b1dfbc61c3..aeb0cdf88c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md @@ -53,8 +53,8 @@ function maybeMutate(x) {} function Component(props) { const $ = _c(11); - let Tag; let T0; + let Tag; let t0; if ($[0] !== props.component || $[1] !== props.alternateComponent) { const maybeMutable = new MaybeMutable(); @@ -64,12 +64,12 @@ function Component(props) { t0 = ((Tag = props.alternateComponent), maybeMutate(maybeMutable)); $[0] = props.component; $[1] = props.alternateComponent; - $[2] = Tag; - $[3] = T0; + $[2] = T0; + $[3] = Tag; $[4] = t0; } else { - Tag = $[2]; - T0 = $[3]; + T0 = $[2]; + Tag = $[3]; t0 = $[4]; } let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md index e172ee1039..bac21217c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md @@ -44,7 +44,7 @@ function CaptureNotMutate(props) { } const idx = t0; let aliasedElement; - if ($[2] !== props.el || $[3] !== idx) { + if ($[2] !== idx || $[3] !== props.el) { const element = bar(props.el); const fn = function () { @@ -54,8 +54,8 @@ function CaptureNotMutate(props) { aliasedElement = fn(); mutate(aliasedElement); - $[2] = props.el; - $[3] = idx; + $[2] = idx; + $[3] = props.el; $[4] = aliasedElement; } else { aliasedElement = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md index 47c7a2d743..af9b1df36a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md @@ -21,10 +21,10 @@ function App() { const { foo } = useContext_withSelector(MyContext, _temp); const { bar } = useContext_withSelector(MyContext, _temp2); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md index 0b12c2e250..d13682467b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md @@ -19,10 +19,10 @@ function App() { const $ = _c(3); const { foo, bar } = useContext_withSelector(MyContext, _temp); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md index 5bf1f2cf4d..e5a9081137 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md @@ -60,7 +60,7 @@ function Component() { t2 = $[3]; } let t3; - if ($[4] !== t1 || $[5] !== t0) { + if ($[4] !== t0 || $[5] !== t1) { t3 = (
{t2} @@ -68,8 +68,8 @@ function Component() { {t0}
); - $[4] = t1; - $[5] = t0; + $[4] = t0; + $[5] = t1; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md index 887479c01e..2fc302c8b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md @@ -43,11 +43,11 @@ function foo(a, b, c) { } const y = t1; let t2; - if ($[4] !== x || $[5] !== y.method || $[6] !== b) { + if ($[4] !== b || $[5] !== x || $[6] !== y.method) { t2 = x[y.method](b); - $[4] = x; - $[5] = y.method; - $[6] = b; + $[4] = b; + $[5] = x; + $[6] = y.method; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md index c32777534b..58c06101d3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md @@ -33,11 +33,11 @@ function foo(a, b, c) { const method = x.method; let t1; - if ($[2] !== method || $[3] !== x || $[4] !== b) { + if ($[2] !== b || $[3] !== method || $[4] !== x) { t1 = method.call(x, b); - $[2] = method; - $[3] = x; - $[4] = b; + $[2] = b; + $[3] = method; + $[4] = x; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md index ad45287d1f..fd8a4935a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md @@ -40,10 +40,10 @@ function foo(a, b, c) { } const x = t0; let t1; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { t1 = x.foo(b); - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md index 090e9d889c..a3403e6c8d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md @@ -73,8 +73,8 @@ import { identity, mutate } from "shared-runtime"; function useFoo(t0) { const $ = _c(4); const { a, b } = t0; - let z; let y; + let z; if ($[0] !== a || $[1] !== b) { const x = { a }; y = {}; @@ -83,11 +83,11 @@ function useFoo(t0) { mutate(y); $[0] = a; $[1] = b; - $[2] = z; - $[3] = y; + $[2] = y; + $[3] = z; } else { - z = $[2]; - y = $[3]; + y = $[2]; + z = $[3]; } if (z[0] !== y) { throw new Error("oh no!"); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md index 4b500f52d6..dc1cd699d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md @@ -40,7 +40,7 @@ function useHook(t0) { const { value } = t0; const [state] = useState(false); let t1; - if ($[0] !== value || $[1] !== state) { + if ($[0] !== state || $[1] !== value) { t1 = { getX() { return { @@ -52,8 +52,8 @@ function useHook(t0) { }; }, }; - $[0] = value; - $[1] = state; + $[0] = state; + $[1] = value; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md index 77875f789d..3dd8e73032 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md index 46767056bd..3cd9877813 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md index 6e44a97b45..60a6171ab1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md index 77ded20d93..2674d78c99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md index 10c23085d8..1d4a50d285 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..42b3b21a20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); - let y; let t0; + let y; if ($[0] !== props) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -57,11 +57,11 @@ function Component(props) { } } $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md index 9ca63e23c4..3da133e929 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(7); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; if ($[4] !== minWidth || $[5] !== width) { @@ -55,9 +55,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md index 947e3bd2eb..ee4e4634cb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md @@ -42,9 +42,9 @@ import { Stringify } from "shared-runtime"; function Foo(t0) { const $ = _c(8); const { arr1, arr2, foo } = t0; - let t1; let getVal1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + let t1; + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -55,13 +55,13 @@ function Foo(t0) { t1 = () => [y]; foo ? (y = x.concat(arr2)) : y; $[0] = arr1; - $[1] = foo; - $[2] = arr2; - $[3] = t1; - $[4] = getVal1; + $[1] = arr2; + $[2] = foo; + $[3] = getVal1; + $[4] = t1; } else { - t1 = $[3]; - getVal1 = $[4]; + getVal1 = $[3]; + t1 = $[4]; } const getVal2 = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md index 3a7016f803..f7353ddd5e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md @@ -44,10 +44,10 @@ function Component(t0) { t3 = $[1]; } let t4; - if ($[2] !== t3 || $[3] !== propA) { + if ($[2] !== propA || $[3] !== t3) { t4 = { value: t3, other: propA }; - $[2] = t3; - $[3] = propA; + $[2] = propA; + $[3] = t3; $[4] = t4; } else { t4 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md index f0ce9b9114..c6ad9bcdac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md @@ -34,10 +34,10 @@ function Component(t0) { const t2 = propB?.x.y; let t3; - if ($[0] !== t2 || $[1] !== propA) { + if ($[0] !== propA || $[1] !== t2) { t3 = { value: t2, other: propA }; - $[0] = t2; - $[1] = propA; + $[0] = propA; + $[1] = t2; $[2] = t3; } else { t3 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md index 9d7feacd6d..7a27bb8521 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(6); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; @@ -58,9 +58,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md index e9d2bffb30..5fc0ec510b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md @@ -44,7 +44,7 @@ function Foo(t0) { const { arr1, arr2, foo } = t0; let t1; let val1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -63,8 +63,8 @@ function Foo(t0) { foo ? (y = x.concat(arr2)) : y; t1 = (() => [y])(); $[0] = arr1; - $[1] = foo; - $[2] = arr2; + $[1] = arr2; + $[2] = foo; $[3] = t1; $[4] = val1; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md index 594e68f24f..de39fc9706 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md @@ -32,15 +32,15 @@ function Component(t0) { const $ = _c(5); const { base, start, increment, test } = t0; let value; - if ($[0] !== base || $[1] !== start || $[2] !== test || $[3] !== increment) { + if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) { value = base; for (let i = start; i < test; i = i + increment, i) { value = value + i; } $[0] = base; - $[1] = start; - $[2] = test; - $[3] = increment; + $[1] = increment; + $[2] = start; + $[3] = test; $[4] = value; } else { value = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md index d0486cd8c2..28612f2d73 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md index b4a55fcb61..2861ab71c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md index f15b9b8e9b..b5db44aa2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md index 324eb714fc..acc72529ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md @@ -32,8 +32,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(6); - let y; let t0; + let y; if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -60,11 +60,11 @@ function Component(props) { $[0] = props.cond; $[1] = props.a; $[2] = props.b; - $[3] = y; - $[4] = t0; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md index c0f8aa97cd..673dd0d5fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md @@ -58,7 +58,7 @@ function useFoo(t0) { local = $[1]; } let t1; - if ($[2] !== shouldReadA || $[3] !== local) { + if ($[2] !== local || $[3] !== shouldReadA) { t1 = ( { @@ -70,8 +70,8 @@ function useFoo(t0) { shouldInvokeFns={true} /> ); - $[2] = shouldReadA; - $[3] = local; + $[2] = local; + $[3] = shouldReadA; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md index e37b8365a2..abf4c98f23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md @@ -41,7 +41,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a) { + if ($[0] !== a || $[1] !== shouldReadA) { t1 = ( { @@ -53,8 +53,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a; + $[0] = a; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md index 89b4d281f8..d82956e4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md @@ -51,13 +51,13 @@ function Foo(t0) { const fn = t1; useIdentity(null); let x; - if ($[2] !== cond || $[3] !== a.b.c) { + if ($[2] !== a.b.c || $[3] !== cond) { x = makeArray(); if (cond) { x.push(identity(a.b.c)); } - $[2] = cond; - $[3] = a.b.c; + $[2] = a.b.c; + $[3] = cond; $[4] = x; } else { x = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md index 591e04de7b..c81e59ecea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md @@ -50,22 +50,22 @@ function Foo(t0) { const fn = t1; useIdentity(null); let arr; - if ($[2] !== cond || $[3] !== a.b?.c.e) { + if ($[2] !== a.b?.c.e || $[3] !== cond) { arr = makeArray(); if (cond) { arr.push(identity(a.b?.c.e)); } - $[2] = cond; - $[3] = a.b?.c.e; + $[2] = a.b?.c.e; + $[3] = cond; $[4] = arr; } else { arr = $[4]; } let t2; - if ($[5] !== fn || $[6] !== arr) { + if ($[5] !== arr || $[6] !== fn) { t2 = ; - $[5] = fn; - $[6] = arr; + $[5] = arr; + $[6] = fn; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md index 902a1578c8..30cea1e2c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md @@ -38,15 +38,15 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(4); let x; - if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { + if ($[0] !== other || $[1] !== props.a.a.a || $[2] !== props.a.b) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a.a.a; - $[1] = props.a.b; - $[2] = other; + $[0] = other; + $[1] = props.a.a.a; + $[2] = props.a.b; $[3] = x; } else { x = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md index 37846215b1..fee31c9faf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md @@ -35,8 +35,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { @@ -65,19 +65,19 @@ function Component(props) { t0 = ; $[0] = props.p0; $[1] = props.p2; - $[2] = y; - $[3] = t0; + $[2] = t0; + $[3] = y; } else { - y = $[2]; - t0 = $[3]; + t0 = $[2]; + y = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md index 1be4143849..6290668015 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md @@ -30,8 +30,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { @@ -48,19 +48,19 @@ function Component(props) { $[0] = props.p0; $[1] = props.p2; $[2] = props.p3; - $[3] = y; - $[4] = t0; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md index 7480362a43..c6331bd4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md @@ -54,10 +54,10 @@ function Component(props) { } const c = t0; let t1; - if ($[3] !== c || $[4] !== a) { + if ($[3] !== a || $[4] !== c) { t1 = [c, a]; - $[3] = c; - $[4] = a; + $[3] = a; + $[4] = c; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md index 29e3e2757f..35637b1a59 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md @@ -41,10 +41,10 @@ function Component(props) { } const count = t1; let t2; - if ($[5] !== items || $[6] !== count) { + if ($[5] !== count || $[6] !== items) { t2 = { items, count }; - $[5] = items; - $[6] = count; + $[5] = count; + $[6] = items; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md index 3408f707d6..bb76df4de0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md @@ -40,10 +40,10 @@ function Component(props) { } const count = t1; let t2; - if ($[4] !== items || $[5] !== count) { + if ($[4] !== count || $[5] !== items) { t2 = { items, count }; - $[4] = items; - $[5] = count; + $[4] = count; + $[5] = items; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md index e682a01086..422f4cf547 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md @@ -42,8 +42,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(10); - let x; let t0; + let x; if ($[0] !== a) { x = []; if (a) { @@ -52,11 +52,11 @@ function foo(a, b, c) { t0 =
{x}
; $[0] = a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const y = t0; bb0: switch (b) { @@ -83,15 +83,15 @@ function foo(a, b, c) { } } let t1; - if ($[7] !== y || $[8] !== x) { + if ($[7] !== x || $[8] !== y) { t1 = (
{y} {x}
); - $[7] = y; - $[8] = x; + $[7] = x; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md index 3ad544344d..f3ddf57ec0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md @@ -34,7 +34,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md index 449aa6d3d8..5700634449 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md @@ -31,9 +31,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..18e9faf63b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== a.b.c || $[1] !== shouldReadA) { t1 = ( { @@ -50,8 +50,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a.b.c; + $[0] = a.b.c; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md index 701702f9dd..2dd9362404 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md @@ -80,10 +80,10 @@ function useFoo(t0) { x = $[6]; } let t1; - if ($[7] !== y || $[8] !== x.a.b) { + if ($[7] !== x.a.b || $[8] !== y) { t1 = [y, x.a.b]; - $[7] = y; - $[8] = x.a.b; + $[7] = x.a.b; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md index b4c25d5f45..e024bc893a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md index 359ba0dcde..055d1ce12e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; for (let i = 0; i < 5; i++) { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md index bb47587687..1c2162352b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md @@ -42,8 +42,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -77,11 +77,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md index 59225b5155..ca8228e2db 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md index 7a7f9a4b6e..ce12fb17b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md @@ -33,9 +33,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md index 2b925c2479..058bf85b69 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md @@ -39,8 +39,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { input, cond } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -61,11 +61,11 @@ function useFoo(t0) { } $[0] = cond; $[1] = input; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md index 291998c8ad..2715a0c92d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md @@ -40,7 +40,7 @@ function useFoo(t0) { const $ = _c(3); const { input, max } = t0; let x; - if ($[0] !== max || $[1] !== input.a.b) { + if ($[0] !== input.a.b || $[1] !== max) { x = []; let i = 0; while (true) { @@ -52,8 +52,8 @@ function useFoo(t0) { x.push(i); x.push(input.a.b); - $[0] = max; - $[1] = input.a.b; + $[0] = input.a.b; + $[1] = max; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md index 84b8fa1d43..845ea4b5ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md @@ -33,8 +33,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, hasAB, returnNull } = t0; - let x; let t1; + let x; if ($[0] !== hasAB || $[1] !== input.a || $[2] !== returnNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -68,11 +68,11 @@ function useFoo(t0) { $[0] = hasAB; $[1] = input.a; $[2] = returnNull; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md index a55922f610..d3e463495b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond2, cond1 } = t0; - let x; let t1; + let x; if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== input.a.b) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond1; $[1] = cond2; $[2] = input.a.b; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..d3a61a1019 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -36,14 +36,14 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== other || $[1] !== props.a) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; + $[0] = other; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md index 01ab4f6b0a..41a7a25d63 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md @@ -34,9 +34,9 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, inputHasAB, inputHasABC } = t0; - let x; let t1; - if ($[0] !== inputHasABC || $[1] !== input.a || $[2] !== inputHasAB) { + let x; + if ($[0] !== input.a || $[1] !== inputHasAB || $[2] !== inputHasABC) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -75,14 +75,14 @@ function useFoo(t0) { x.push(t2); } } - $[0] = inputHasABC; - $[1] = input.a; - $[2] = inputHasAB; - $[3] = x; - $[4] = t1; + $[0] = input.a; + $[1] = inputHasAB; + $[2] = inputHasABC; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md index a62223d72d..03e3e96397 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md @@ -40,14 +40,14 @@ import { identity } from "shared-runtime"; function useConditionalSubpath1(props, cond) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.b = props.a.b; if (identity(cond)) { x.a = props.a; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md index 02117feee2..5b246175f9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md @@ -48,14 +48,14 @@ function useConditionalSuperpath1(t0) { const $ = _c(3); const { props, cond } = t0; let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.a = props.a; if (identity(cond)) { x.b = props.a.b; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md index 34979e9de9..c91cf94445 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md @@ -49,15 +49,15 @@ function Component(t0) { const $ = _c(8); const { cond, other } = t0; let x; - if ($[0] !== other || $[1] !== cond) { + if ($[0] !== cond || $[1] !== other) { x = makeObject_Primitives(); setProperty(x, { b: 3, other }, "a"); identity(x.a.b); if (!cond) { x.a = null; } - $[0] = other; - $[1] = cond; + $[0] = cond; + $[1] = other; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md index da6912d35e..a2bd99e9a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md @@ -82,10 +82,10 @@ function Component(t0) { } const b = t3; let t4; - if ($[4] !== b || $[5] !== a) { + if ($[4] !== a || $[5] !== b) { t4 = { b, a }; - $[4] = b; - $[5] = a; + $[4] = a; + $[5] = b; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md index bba0b3f139..0089d20af2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md @@ -59,7 +59,7 @@ function Component(t0) { const serverTime = useServerTime(); let t1; let timestampLabel; - if ($[0] !== highlightedItem || $[1] !== serverTime || $[2] !== label) { + if ($[0] !== highlightedItem || $[1] !== label || $[2] !== serverTime) { const highlight = new Highlight(highlightedItem); const time = serverTime.get(); @@ -68,8 +68,8 @@ function Component(t0) { t1 = highlight.render(); $[0] = highlightedItem; - $[1] = serverTime; - $[2] = label; + $[1] = label; + $[2] = serverTime; $[3] = t1; $[4] = timestampLabel; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md index 9c21dc8400..ecdfa88b98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md @@ -66,10 +66,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md index 1b6e91a6fd..f9d725e0b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md @@ -65,10 +65,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md index 2dabc256f9..1ad6f6a047 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md @@ -71,10 +71,10 @@ function Foo() { const shouldCaptureObj = obj != null && CONST_TRUE; const t0 = shouldCaptureObj ? identity(obj) : null; let t1; - if ($[0] !== t0 || $[1] !== obj) { + if ($[0] !== obj || $[1] !== t0) { t1 = [t0, obj]; - $[0] = t0; - $[1] = obj; + $[0] = obj; + $[1] = t0; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md index 4921fd340b..d35cbe266f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md @@ -77,15 +77,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md index 80d046ec18..eda7a0cbfe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md @@ -74,15 +74,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md index 5eff1aa0fe..b5a7827728 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md @@ -21,13 +21,13 @@ function Component(listItem, thread) { let t0; let t1; let t2; - if ($[0] !== thread.threadType || $[1] !== listItem) { + if ($[0] !== listItem || $[1] !== thread.threadType) { const isFoo = isFooThread(thread.threadType); t1 = useBar; t2 = listItem; t0 = getBadgeText(listItem, isFoo); - $[0] = thread.threadType; - $[1] = listItem; + $[0] = listItem; + $[1] = thread.threadType; $[2] = t0; $[3] = t1; $[4] = t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md index 1d4a4b5d67..32cbbb2b91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md @@ -28,7 +28,7 @@ function V0(t0) { const { v1, v2 } = t0; const v5 = v1.v6?.v7; let t1; - if ($[0] !== v5 || $[1] !== v1 || $[2] !== v2) { + if ($[0] !== v1 || $[1] !== v2 || $[2] !== v5) { t1 = ( {v5 != null ? ( @@ -40,9 +40,9 @@ function V0(t0) { )} ); - $[0] = v5; - $[1] = v1; - $[2] = v2; + $[0] = v1; + $[1] = v2; + $[2] = v5; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md index cec64e8cf0..ab409b366d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md @@ -36,7 +36,7 @@ function useTest(t0) { const $ = _c(3); const { isNull, data } = t0; let t1; - if ($[0] !== isNull || $[1] !== data) { + if ($[0] !== data || $[1] !== isNull) { t1 = Builder.makeBuilder(isNull, "hello world") ?.push("1", 2) ?.push(3, { a: 4, b: 5, c: data }) @@ -47,8 +47,8 @@ function useTest(t0) { ) ?.push(7, "8") ?.push("8", Builder.makeBuilder(!isNull)?.push(9).vals)?.vals; - $[0] = isNull; - $[1] = data; + $[0] = data; + $[1] = isNull; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md index 73674e5fff..496d80d52b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md @@ -77,10 +77,10 @@ function Component(t0) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== result) { + if ($[4] !== result || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = result; + $[4] = result; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md index 2e9daceed7..1b49552bea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md @@ -26,8 +26,8 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); const item = props.item; - let t0; let baseVideos; + let t0; let thumbnails; if ($[0] !== item) { thumbnails = []; @@ -40,12 +40,12 @@ function Component(props) { } }); $[0] = item; - $[1] = t0; - $[2] = baseVideos; + $[1] = baseVideos; + $[2] = t0; $[3] = thumbnails; } else { - t0 = $[1]; - baseVideos = $[2]; + baseVideos = $[1]; + t0 = $[2]; thumbnails = $[3]; } t0 = undefined; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md index 9aa75b2162..aece9665c9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const [bar] = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md index ded1eb4006..3681e9c469 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const bar = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md index f0a46be168..3e66b20041 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const { bar } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md index 7c9c6a5028..1201a9a737 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md @@ -73,14 +73,14 @@ function foo(props) { } const header = t0; let y; - if ($[5] !== x || $[6] !== props.b || $[7] !== props.c) { + if ($[5] !== props.b || $[6] !== props.c || $[7] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[5] = x; - $[6] = props.b; - $[7] = props.c; + $[5] = props.b; + $[6] = props.c; + $[7] = x; $[8] = y; $[9] = x; } else { @@ -103,15 +103,15 @@ function foo(props) { } const content = t1; let t2; - if ($[13] !== header || $[14] !== content) { + if ($[13] !== content || $[14] !== header) { t2 = ( <> {header} {content} ); - $[13] = header; - $[14] = content; + $[13] = content; + $[14] = header; $[15] = t2; } else { t2 = $[15]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md index 36b1d4541a..143496678e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md @@ -53,30 +53,30 @@ import { c as _c } from "react/compiler-runtime"; // note: comments are for the // emitted function foo(props) { const $ = _c(14); - let x; let t0; + let x; if ($[0] !== props.a) { x = []; x.push(props.a); t0 =
{x}
; $[0] = props.a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const header = t0; let y; - if ($[3] !== x || $[4] !== props.b || $[5] !== props.c) { + if ($[3] !== props.b || $[4] !== props.c || $[5] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[3] = x; - $[4] = props.b; - $[5] = props.c; + $[3] = props.b; + $[4] = props.c; + $[5] = x; $[6] = y; $[7] = x; } else { @@ -99,15 +99,15 @@ function foo(props) { } const content = t1; let t2; - if ($[11] !== header || $[12] !== content) { + if ($[11] !== content || $[12] !== header) { t2 = ( <> {header} {content} ); - $[11] = header; - $[12] = content; + $[11] = content; + $[12] = header; $[13] = t2; } else { t2 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md index 5f10f44202..36a68d07c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md @@ -43,9 +43,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); - let text; let t0; let t1; + let text; if ($[0] !== statusName) { const { status, text: t2 } = foo(statusName); text = t2; @@ -54,13 +54,13 @@ function Component(statusName) { t1 = identity(bg); t0 = identity(color); $[0] = statusName; - $[1] = text; - $[2] = t0; - $[3] = t1; + $[1] = t0; + $[2] = t1; + $[3] = text; } else { - text = $[1]; - t0 = $[2]; - t1 = $[3]; + t0 = $[1]; + t1 = $[2]; + text = $[3]; } let t2; if ($[4] !== text) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md index e2cd53bd0d..d8e991dc46 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md @@ -46,9 +46,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); + let font; let t0; let text; - let font; if ($[0] !== statusName) { const { status, text: t1 } = foo(statusName); text = t1; @@ -58,13 +58,13 @@ function Component(statusName) { t0 = identity(color); $[0] = statusName; - $[1] = t0; - $[2] = text; - $[3] = font; + $[1] = font; + $[2] = t0; + $[3] = text; } else { - t0 = $[1]; - text = $[2]; - font = $[3]; + font = $[1]; + t0 = $[2]; + text = $[3]; } const bg = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..788109636b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -34,8 +34,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); - let y; let t0; + let y; if ($[0] !== props) { const x = []; bb0: switch (props.p0) { @@ -63,19 +63,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[4] !== child || $[5] !== y) { t1 = {child}; - $[4] = y; - $[5] = child; + $[4] = child; + $[5] = y; $[6] = t1; } else { t1 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..2628982655 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -29,8 +29,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(6); - let y; let t0; + let y; if ($[0] !== props) { const x = []; switch (props.p0) { @@ -45,19 +45,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[3] !== child || $[4] !== y) { t1 = {child}; - $[3] = y; - $[4] = child; + $[3] = child; + $[4] = y; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md index f106382d64..4864c51a1f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -64,8 +64,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -85,15 +85,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md index 77fd38aea1..c661094fdd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md @@ -55,7 +55,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -70,8 +70,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,15 +91,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md index d064a48b71..7ac6486b47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md @@ -18,10 +18,10 @@ function App() { const $ = _c(3); const [foo, bar] = useContext(MyContext); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md index f82af06866..3eac66304b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md @@ -22,10 +22,10 @@ function App() { const { foo } = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md index 573b6db231..4cca8b19d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md @@ -22,10 +22,10 @@ function App() { const [foo] = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md index 03ce7f97ba..f5a3916626 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md @@ -22,10 +22,10 @@ function App() { const { joe: t0, bar } = useContext(MyContext); const { foo } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md index 55387503cf..0888d67b2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md @@ -22,10 +22,10 @@ function App() { const foo = context.foo; const bar = context.bar; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md index 268fa8d7eb..2c27360c9f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md @@ -42,9 +42,9 @@ import { mutate, setProperty, throwErrorWithMessageIf } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { value, cond } = t0; - let y; let t1; - if ($[0] !== value || $[1] !== cond) { + let y; + if ($[0] !== cond || $[1] !== value) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { y = [value]; @@ -68,13 +68,13 @@ function useFoo(t0) { } y.push(x); } - $[0] = value; - $[1] = cond; - $[2] = y; - $[3] = t1; + $[0] = cond; + $[1] = value; + $[2] = t1; + $[3] = y; } else { - y = $[2]; - t1 = $[3]; + t1 = $[2]; + y = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md index b04bd53458..562c0bc1c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md @@ -32,8 +32,8 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -47,11 +47,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md index af5f2ebfcf..71a59aba2f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md @@ -33,8 +33,8 @@ const { shallowCopy, throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -52,11 +52,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md index 54d5be2d6b..c3c45beb86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md @@ -78,10 +78,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -95,10 +95,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md index 072c6d03d9..4acbd2dfdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md @@ -76,10 +76,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -93,10 +93,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md index caa74267f3..5016b0c4df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md index a92abd4ca5..3f341fc665 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md index fbd13dc1b0..7fa86838e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md @@ -38,22 +38,22 @@ function component(a) { const f = typeof t.t; let t0; if ( - $[0] !== z || - $[1] !== p || - $[2] !== q || + $[0] !== e || + $[1] !== f || + $[2] !== m || $[3] !== n || - $[4] !== m || - $[5] !== e || - $[6] !== f + $[4] !== p || + $[5] !== q || + $[6] !== z ) { t0 = { z, p, q, n, m, e, f }; - $[0] = z; - $[1] = p; - $[2] = q; + $[0] = e; + $[1] = f; + $[2] = m; $[3] = n; - $[4] = m; - $[5] = e; - $[6] = f; + $[4] = p; + $[5] = q; + $[6] = z; $[7] = t0; } else { t0 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md index 663a6788f3..7f79cae4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md @@ -89,10 +89,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..d94a5e7e37 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -109,10 +109,10 @@ function Inner(props) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== output) { + if ($[4] !== output || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = output; + $[4] = output; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md index 99effce934..cf0093ea94 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md @@ -91,10 +91,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md index 1292ea1489..c3e115fa0d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md @@ -51,7 +51,7 @@ function Component(props) { } const exit = t0; let t1; - if ($[2] !== item.value || $[3] !== exit) { + if ($[2] !== exit || $[3] !== item.value) { t1 = () => { const cleanup = GlobalEventEmitter.addListener("onInput", () => { if (item.value) { @@ -60,8 +60,8 @@ function Component(props) { }); return () => cleanup.remove(); }; - $[2] = item.value; - $[3] = exit; + $[2] = exit; + $[3] = item.value; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md index b6a4187355..28d3e33359 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md @@ -62,13 +62,13 @@ function Component(props) { {w} ); - let condition = $[2] !== x || $[3] !== w; + let condition = $[2] !== w || $[3] !== x; if (!condition) { let old$t1 = $[4]; $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(7:10)"); } - $[2] = x; - $[3] = w; + $[2] = w; + $[3] = x; $[4] = t1; if (condition) { t1 = ( From 55d587e2c033ee38f9ecb5a158fc3e761f224d22 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:06 -0500 Subject: [PATCH 065/422] [compiler] patch: rewrite scope dep/decl in inlineJsxTransform Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: --- .../src/Optimization/InlineJsxTransform.ts | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts index 50822e78d9..d97a4da1ec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts @@ -383,6 +383,30 @@ export function inlineJsxTransform( mapTerminalOperands(block.terminal, place => handlePlace(place, blockId, inlinedJsxDeclarations), ); + + if (block.terminal.kind === 'scope') { + const scope = block.terminal.scope; + for (const dep of scope.dependencies) { + dep.identifier = handleIdentifier( + dep.identifier, + inlinedJsxDeclarations, + ); + } + + for (const [origId, decl] of [...scope.declarations]) { + const newDecl = handleIdentifier( + decl.identifier, + inlinedJsxDeclarations, + ); + if (newDecl.id !== origId) { + scope.declarations.delete(origId); + scope.declarations.set(decl.identifier.id, { + identifier: newDecl, + scope: decl.scope, + }); + } + } + } } /** @@ -697,10 +721,10 @@ function handlePlace( inlinedJsxDeclaration == null || inlinedJsxDeclaration.blockIdsToIgnore.has(blockId) ) { - return {...place}; + return place; } - return {...place, identifier: {...inlinedJsxDeclaration.identifier}}; + return {...place, identifier: inlinedJsxDeclaration.identifier}; } function handlelValue( @@ -715,8 +739,20 @@ function handlelValue( inlinedJsxDeclaration == null || inlinedJsxDeclaration.blockIdsToIgnore.has(blockId) ) { - return {...lvalue}; + return lvalue; } - return {...lvalue, identifier: {...inlinedJsxDeclaration.identifier}}; + return {...lvalue, identifier: inlinedJsxDeclaration.identifier}; +} + +function handleIdentifier( + identifier: Identifier, + inlinedJsxDeclarations: InlinedJsxDeclarationMap, +): Identifier { + const inlinedJsxDeclaration = inlinedJsxDeclarations.get( + identifier.declarationId, + ); + return inlinedJsxDeclaration == null + ? identifier + : inlinedJsxDeclaration.identifier; } From b62e061f4d5f2e17009570f76410e590cd2bfe5c Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 066/422] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR ' --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1869 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 3e2b5597ac..ce38e91af4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,16 +231,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..352552bf02 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 71cc997f542be58f85c427c4a111cb13595bb014 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 067/422] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200 Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From d62257fbe5b4f6d6d2f0ffa35c06e55c0391fce6 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 068/422] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index ce38e91af4..fab2111735 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,6 +231,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 02e60eff91..ed56ff0681 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 157e2de81a..bb99a5d90f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From 8096620228bd90c6802cb6b4c17cd73aae370a89 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 069/422] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 9494436d1f..ecc22365dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From 9cf9ea793ebd9a029dede5de027ef35e3a7b772d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 070/422] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From dc8138d8df1212de545bf1bf82b57bcdf4af8e65 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 071/422] [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 f035184b7f24ca7b830259b3274bf3ad1a0c13a5 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 072/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../src/SSA/EliminateRedundantPhi.ts | 19 +++ .../src/SSA/EnterSSA.ts | 3 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 38 files changed, 130 insertions(+), 335 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index ecc22365dd..41670b1e81 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index fab2111735..f529e2cefe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -232,7 +232,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 954fb6f400..2f5c387645 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..37394daa8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -13,6 +13,8 @@ import { eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +143,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 13752bf974a248f2ae5623a07ecf6795b277325e Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:06 -0500 Subject: [PATCH 073/422] [compiler] patch: rewrite scope dep/decl in inlineJsxTransform This bugfix is needed to land #31199 PropagateScopeDepsHIR infers scope declarations for the `inline-jsx-transform` test fixture (the non-hir version does not). These declarations must get the rewritten phi identifiers --- .../src/Optimization/InlineJsxTransform.ts | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts index 50822e78d9..d97a4da1ec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts @@ -383,6 +383,30 @@ export function inlineJsxTransform( mapTerminalOperands(block.terminal, place => handlePlace(place, blockId, inlinedJsxDeclarations), ); + + if (block.terminal.kind === 'scope') { + const scope = block.terminal.scope; + for (const dep of scope.dependencies) { + dep.identifier = handleIdentifier( + dep.identifier, + inlinedJsxDeclarations, + ); + } + + for (const [origId, decl] of [...scope.declarations]) { + const newDecl = handleIdentifier( + decl.identifier, + inlinedJsxDeclarations, + ); + if (newDecl.id !== origId) { + scope.declarations.delete(origId); + scope.declarations.set(decl.identifier.id, { + identifier: newDecl, + scope: decl.scope, + }); + } + } + } } /** @@ -697,10 +721,10 @@ function handlePlace( inlinedJsxDeclaration == null || inlinedJsxDeclaration.blockIdsToIgnore.has(blockId) ) { - return {...place}; + return place; } - return {...place, identifier: {...inlinedJsxDeclaration.identifier}}; + return {...place, identifier: inlinedJsxDeclaration.identifier}; } function handlelValue( @@ -715,8 +739,20 @@ function handlelValue( inlinedJsxDeclaration == null || inlinedJsxDeclaration.blockIdsToIgnore.has(blockId) ) { - return {...lvalue}; + return lvalue; } - return {...lvalue, identifier: {...inlinedJsxDeclaration.identifier}}; + return {...lvalue, identifier: inlinedJsxDeclaration.identifier}; +} + +function handleIdentifier( + identifier: Identifier, + inlinedJsxDeclarations: InlinedJsxDeclarationMap, +): Identifier { + const inlinedJsxDeclaration = inlinedJsxDeclarations.get( + identifier.declarationId, + ); + return inlinedJsxDeclaration == null + ? identifier + : inlinedJsxDeclaration.identifier; } From de4b0a801fa6c5d5bcf2612a97663ab80651b4b4 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 074/422] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR ' --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1869 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 3e2b5597ac..ce38e91af4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,16 +231,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..352552bf02 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 00a53cc3bdf33babbcfeeb4788af18c4ac5bdbb9 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 075/422] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200 Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From 2b51f3b3706cc6e7de50f3df2d813e5e84411b25 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 076/422] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index ce38e91af4..fab2111735 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,6 +231,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 02e60eff91..ed56ff0681 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md index 157e2de81a..bb99a5d90f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From 640a785dc8000317095bc51f235af746ae254787 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 077/422] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 9494436d1f..ecc22365dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From 98b0d00ce8025cb1f22adf257054df99146f0ad1 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 078/422] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From cdf5e83f28a5b7018364b54389a285aba1cc5693 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 079/422] [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 cd06aee7825f5634949124e73d3e0e28ad36941d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:10:17 -0500 Subject: [PATCH 080/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../src/SSA/EliminateRedundantPhi.ts | 19 +++ .../src/SSA/EnterSSA.ts | 3 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 38 files changed, 130 insertions(+), 335 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index ecc22365dd..41670b1e81 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index fab2111735..f529e2cefe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -232,7 +232,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 954fb6f400..2f5c387645 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..37394daa8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -13,6 +13,8 @@ import { eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +143,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From e88b05e387a5793a33c9541f3f7bc749befd2c1f Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:15:40 -0500 Subject: [PATCH 081/422] [compiler][ez] Clean up pragma parsing for tests + playground Move environment config parsing for `inlineJsxTransform`, `lowerContextAccess`, and some dev-only options out of snap (test fixture). These should now be available for playground via `@inlineJsxTransform` and `lowerContextAccess`. Other small change: Changed zod fields from `nullish()` -> `nullable().default(null)`. [`nullish`](https://zod.dev/?id=nullish) fields accept `null | undefined` and default to `undefined`. We don't distinguish between null and undefined for any of these options, so let's only accept null + default to null. This also makes EnvironmentConfig in the playground more accurate. Previously, some fields just didn't show up as `prettyFormat({field: undefined})` does not print `field`. --- .../src/HIR/Environment.ts | 93 +++++++++++++------ ...codegen-emit-imports-same-source.expect.md | 4 +- .../codegen-emit-imports-same-source.js | 2 +- ...en-instrument-forget-gating-test.expect.md | 4 +- .../codegen-instrument-forget-gating-test.js | 2 +- .../codegen-instrument-forget-test.expect.md | 4 +- .../codegen-instrument-forget-test.js | 2 +- .../compiler/inline-jsx-transform.expect.md | 4 +- .../fixtures/compiler/inline-jsx-transform.js | 2 +- compiler/packages/snap/src/compiler.ts | 72 +++----------- 10 files changed, 88 insertions(+), 101 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 3e2b5597ac..8130e46631 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -69,8 +69,8 @@ export const ExternalFunctionSchema = z.object({ export const InstrumentationSchema = z .object({ fn: ExternalFunctionSchema, - gating: ExternalFunctionSchema.nullish(), - globalGating: z.string().nullish(), + gating: ExternalFunctionSchema.nullable(), + globalGating: z.string().nullable(), }) .refine( opts => opts.gating != null || opts.globalGating != null, @@ -147,7 +147,7 @@ export type Hook = z.infer; */ const EnvironmentConfigSchema = z.object({ - customHooks: z.map(z.string(), HookSchema).optional().default(new Map()), + customHooks: z.map(z.string(), HookSchema).default(new Map()), /** * A function that, given the name of a module, can optionally return a description @@ -248,7 +248,7 @@ const EnvironmentConfigSchema = z.object({ * * The symbol configuration is set for backwards compatability with pre-React 19 transforms */ - inlineJsxTransform: ReactElementSymbolSchema.nullish(), + inlineJsxTransform: ReactElementSymbolSchema.nullable().default(null), /* * Enable validation of hooks to partially check that the component honors the rules of hooks. @@ -339,9 +339,9 @@ const EnvironmentConfigSchema = z.object({ * } * } */ - enableEmitFreeze: ExternalFunctionSchema.nullish(), + enableEmitFreeze: ExternalFunctionSchema.nullable().default(null), - enableEmitHookGuards: ExternalFunctionSchema.nullish(), + enableEmitHookGuards: ExternalFunctionSchema.nullable().default(null), /** * Enable instruction reordering. See InstructionReordering.ts for the details @@ -425,7 +425,7 @@ const EnvironmentConfigSchema = z.object({ * } * */ - enableEmitInstrumentForget: InstrumentationSchema.nullish(), + enableEmitInstrumentForget: InstrumentationSchema.nullable().default(null), // Enable validation of mutable ranges assertValidMutableRanges: z.boolean().default(false), @@ -464,8 +464,6 @@ const EnvironmentConfigSchema = z.object({ */ throwUnknownException__testonly: z.boolean().default(false), - enableSharedRuntime__testonly: z.boolean().default(false), - /** * Enables deps of a function epxression to be treated as conditional. This * makes sure we don't load a dep when it's a property (to check if it has @@ -503,7 +501,8 @@ const EnvironmentConfigSchema = z.object({ * computed one. This detects cases where rules of react violations may cause the * compiled code to behave differently than the original. */ - enableChangeDetectionForDebugging: ExternalFunctionSchema.nullish(), + enableChangeDetectionForDebugging: + ExternalFunctionSchema.nullable().default(null), /** * The react native re-animated library uses custom Babel transforms that @@ -543,7 +542,7 @@ const EnvironmentConfigSchema = z.object({ * * Here the variables `ref` and `myRef` will be typed as Refs. */ - enableTreatRefLikeIdentifiersAsRefs: z.boolean().nullable().default(false), + enableTreatRefLikeIdentifiersAsRefs: z.boolean().default(false), /* * If specified a value, the compiler lowers any calls to `useContext` to use @@ -565,11 +564,56 @@ const EnvironmentConfigSchema = z.object({ * const {foo, bar} = useCompiledContext(MyContext, (c) => [c.foo, c.bar]); * ``` */ - lowerContextAccess: ExternalFunctionSchema.nullish(), + lowerContextAccess: ExternalFunctionSchema.nullable().default(null), }); export type EnvironmentConfig = z.infer; +/** + * For test fixtures and playground only. + * + * Pragmas are straightforward to parse for boolean options (`:true` and + * `:false`). These are 'enabled' config values for non-boolean configs (i.e. + * what is used when parsing `:true`). + */ +const testConfigValues: PartialEnvironmentConfig = { + validateNoCapitalizedCalls: [], + enableChangeDetectionForDebugging: { + source: 'react-compiler-runtime', + importSpecifierName: '$structuralCheck', + }, + enableEmitFreeze: { + source: 'react-compiler-runtime', + importSpecifierName: 'makeReadOnly', + }, + enableEmitInstrumentForget: { + fn: { + source: 'react-compiler-runtime', + importSpecifierName: 'useRenderCounter', + }, + gating: { + source: 'react-compiler-runtime', + importSpecifierName: 'shouldInstrument', + }, + globalGating: '__DEV__', + }, + enableEmitHookGuards: { + source: 'react-compiler-runtime', + importSpecifierName: '$dispatcherGuard', + }, + inlineJsxTransform: { + elementSymbol: 'react.transitional.element', + globalDevVar: 'DEV', + }, + lowerContextAccess: { + source: 'react-compiler-runtime', + importSpecifierName: 'useContext_withSelector', + }, +}; + +/** + * For snap test fixtures and playground only. + */ export function parseConfigPragma(pragma: string): EnvironmentConfig { const maybeConfig: any = {}; // Get the defaults to programmatically check for boolean properties @@ -580,21 +624,12 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { continue; } const keyVal = token.slice(1); - let [key, val]: any = keyVal.split(':'); + let [key, val = undefined] = keyVal.split(':'); + const isSet = val === undefined || val === 'true'; - if (key === 'validateNoCapitalizedCalls') { - maybeConfig[key] = []; - continue; - } - - if ( - key === 'enableChangeDetectionForDebugging' && - (val === undefined || val === 'true') - ) { - maybeConfig[key] = { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }; + if (isSet && key in testConfigValues) { + maybeConfig[key] = + testConfigValues[key as keyof PartialEnvironmentConfig]; continue; } @@ -609,7 +644,6 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { props.push({type: 'name', name: elt}); } } - console.log([valSplit[0], props.map(x => x.name ?? '*').join('.')]); maybeConfig[key] = [[valSplit[0], props]]; } continue; @@ -620,11 +654,10 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { continue; } if (val === undefined || val === 'true') { - val = true; + maybeConfig[key] = true; } else { - val = false; + maybeConfig[key] = false; } - maybeConfig[key] = val; } const config = EnvironmentConfigSchema.safeParse(maybeConfig); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md index dd67bcfbff..9a59b36cc0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitFreeze @instrumentForget +// @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { return foo(props.x); @@ -18,7 +18,7 @@ import { shouldInstrument, makeReadOnly, } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget +import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { if (__DEV__ && shouldInstrument) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js index 4edff1c3fc..bd66353319 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js @@ -1,4 +1,4 @@ -// @enableEmitFreeze @instrumentForget +// @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { return foo(props.x); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md index 4aa29992eb..fc9247344d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @instrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode(annotation) @gating function Bar(props) { 'use forget'; @@ -25,7 +25,7 @@ function Foo(props) { ```javascript import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) @gating +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js index 85fbd97ee7..dffb8ce795 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js @@ -1,4 +1,4 @@ -// @instrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode(annotation) @gating function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md index ba8ed5056b..b5da853b6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @instrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { 'use forget'; @@ -24,7 +24,7 @@ function Foo(props) { ```javascript import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js index 8947503277..2aef527e6b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js @@ -1,4 +1,4 @@ -// @instrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md index e657e36d36..f622b3a6fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableInlineJsxTransform +// @inlineJsxTransform function Parent({children, a: _a, b: _b, c: _c, ref}) { return
{children}
; @@ -76,7 +76,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c2 } from "react/compiler-runtime"; // @enableInlineJsxTransform +import { c as _c2 } from "react/compiler-runtime"; // @inlineJsxTransform function Parent(t0) { const $ = _c2(2); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js index bebb7ad53b..ca55cab4ff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js @@ -1,4 +1,4 @@ -// @enableInlineJsxTransform +// @inlineJsxTransform function Parent({children, a: _a, b: _b, c: _c, ref}) { return
{children}
; diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index f0ee88f06e..74737f96a0 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -21,7 +21,6 @@ import type { } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; import type { - EnvironmentConfig, Macro, MacroMethod, parseConfigPragma as ParseConfigPragma, @@ -37,6 +36,11 @@ export function parseLanguage(source: string): 'flow' | 'typescript' { return source.indexOf('@flow') !== -1 ? 'flow' : 'typescript'; } +/** + * Parse react compiler plugin + environment options from test fixture. Note + * that although this primarily uses `Environment:parseConfigPragma`, it also + * has test fixture specific (i.e. not applicable to playground) parsing logic. + */ function makePluginOptions( firstLine: string, parseConfigPragmaFn: typeof ParseConfigPragma, @@ -44,15 +48,11 @@ function makePluginOptions( ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { let gating = null; - let enableEmitInstrumentForget = null; - let enableEmitFreeze = null; - let enableEmitHookGuards = null; let compilationMode: CompilationMode = 'all'; let panicThreshold: PanicThresholdOptions = 'all_errors'; let hookPattern: string | null = null; // TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false let validatePreserveExistingMemoizationGuarantees = false; - let enableChangeDetectionForDebugging = null; let customMacros: null | Array = null; let validateBlocklistedImports = null; let target = '19' as const; @@ -78,31 +78,6 @@ function makePluginOptions( importSpecifierName: 'isForgetEnabled_Fixtures', }; } - if (firstLine.includes('@instrumentForget')) { - enableEmitInstrumentForget = { - fn: { - source: 'react-compiler-runtime', - importSpecifierName: 'useRenderCounter', - }, - gating: { - source: 'react-compiler-runtime', - importSpecifierName: 'shouldInstrument', - }, - globalGating: '__DEV__', - }; - } - if (firstLine.includes('@enableEmitFreeze')) { - enableEmitFreeze = { - source: 'react-compiler-runtime', - importSpecifierName: 'makeReadOnly', - }; - } - if (firstLine.includes('@enableEmitHookGuards')) { - enableEmitHookGuards = { - source: 'react-compiler-runtime', - importSpecifierName: '$dispatcherGuard', - }; - } const targetMatch = /@target="([^"]+)"/.exec(firstLine); if (targetMatch) { @@ -132,16 +107,18 @@ function makePluginOptions( ignoreUseNoForget = true; } + /** + * Snap currently runs all fixtures without `validatePreserveExistingMemo` as + * most fixtures are interested in compilation output, not whether the + * compiler was able to preserve existing memo. + * + * TODO: flip the default. `useMemo` is rare in test fixtures -- fixtures that + * use useMemo should be explicit about whether this flag is enabled + */ if (firstLine.includes('@validatePreserveExistingMemoizationGuarantees')) { validatePreserveExistingMemoizationGuarantees = true; } - if (firstLine.includes('@enableChangeDetectionForDebugging')) { - enableChangeDetectionForDebugging = { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }; - } const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine); if ( hookPatternMatch && @@ -197,22 +174,6 @@ function makePluginOptions( .filter(s => s.length > 0); } - let lowerContextAccess = null; - if (firstLine.includes('@lowerContextAccess')) { - lowerContextAccess = { - source: 'react-compiler-runtime', - importSpecifierName: 'useContext_withSelector', - }; - } - - let inlineJsxTransform: EnvironmentConfig['inlineJsxTransform'] = null; - if (firstLine.includes('@enableInlineJsxTransform')) { - inlineJsxTransform = { - elementSymbol: 'react.transitional.element', - globalDevVar: 'DEV', - }; - } - let logs: Array<{filename: string | null; event: LoggerEvent}> = []; let logger: Logger | null = null; if (firstLine.includes('@logger')) { @@ -232,17 +193,10 @@ function makePluginOptions( ValueKindEnum, }), customMacros, - enableEmitFreeze, - enableEmitInstrumentForget, - enableEmitHookGuards, assertValidMutableRanges: true, - enableSharedRuntime__testonly: true, hookPattern, validatePreserveExistingMemoizationGuarantees, - enableChangeDetectionForDebugging, - lowerContextAccess, validateBlocklistedImports, - inlineJsxTransform, }, compilationMode, logger, From 56c419397a969f9fc90ef8fad61e17450dd05cc2 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:16:19 -0500 Subject: [PATCH 082/422] [compiler] Add fixture for objectexpr computed key bug We're currently bailing out on complex computed-key syntax as we assumed that caused bugs (due to inferring computed key rvalues to have `freeze` effects). This fixture shows that this bailout is unrelated to the underlying bug --- ...nstruction-hoisted-sequence-expr.expect.md | 109 ++++++++++++++++++ ...fter-construction-hoisted-sequence-expr.js | 31 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 141 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md new file mode 100644 index 0000000000..dcca6cddd8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md @@ -0,0 +1,109 @@ + +## Input + +```javascript +import {identity, mutate} from 'shared-runtime'; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const key = {}; + const tmp = (mutate(key), key); + const context = { + // Here, `tmp` is frozen (as it's inferred to be a primitive/string) + [tmp]: identity([props.value]), + }; + mutate(key); + return [context, key]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 42}], + sequentialRenders: [{value: 42}, {value: 42}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { identity, mutate } from "shared-runtime"; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const $ = _c(8); + let t0; + let key; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + key = {}; + t0 = (mutate(key), key); + $[0] = t0; + $[1] = key; + } else { + t0 = $[0]; + key = $[1]; + } + const tmp = t0; + let t1; + if ($[2] !== props.value) { + t1 = identity([props.value]); + $[2] = props.value; + $[3] = t1; + } else { + t1 = $[3]; + } + let t2; + if ($[4] !== t1) { + t2 = { [tmp]: t1 }; + $[4] = t1; + $[5] = t2; + } else { + t2 = $[5]; + } + const context = t2; + + mutate(key); + let t3; + if ($[6] !== context) { + t3 = [context, key]; + $[6] = context; + $[7] = t3; + } else { + t3 = $[7]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], + sequentialRenders: [{ value: 42 }, { value: 42 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js new file mode 100644 index 0000000000..94befbdd17 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js @@ -0,0 +1,31 @@ +import {identity, mutate} from 'shared-runtime'; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const key = {}; + const tmp = (mutate(key), key); + const context = { + // Here, `tmp` is frozen (as it's inferred to be a primitive/string) + [tmp]: identity([props.value]), + }; + mutate(key); + return [context, key]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 42}], + sequentialRenders: [{value: 42}, {value: 42}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..76914f1dd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -478,6 +478,7 @@ const skipFilter = new Set([ // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', + 'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr', 'bug-invalid-hoisting-functionexpr', 'bug-try-catch-maybe-null-dependency', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', From 26554dd0c23ef185e3c1ee0f0e111eaae0d58a96 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:06:55 -0500 Subject: [PATCH 083/422] [compiler][be] Stabilize compiler output: sort deps and decls by name All dependencies and declarations of a reactive scope can be reordered to scope start/end. i.e. generated code does not depend on conditional short-circuiting logic as dependencies are inferred to have no side effects. Sorting these by name helps us get higher signal compilation snapshot diffs when upgrading the compiler and testing PRs internally at Meta --- .../ReactiveScopes/CodegenReactiveFunction.ts | 51 ++++++++++++++++++- ...ng-primitive-as-dep-nested-scope.expect.md | 6 +-- .../compiler/array-at-effect.expect.md | 6 +-- .../array-expression-spread.expect.md | 6 +-- .../compiler/array-property-call.expect.md | 10 ++-- .../bug-invalid-phi-as-dependency.expect.md | 6 +-- ...fun-alias-captured-mutate-2-iife.expect.md | 6 +-- ...ring-fun-alias-captured-mutate-2.expect.md | 6 +-- ...alias-captured-mutate-arr-2-iife.expect.md | 6 +-- ...-fun-alias-captured-mutate-arr-2.expect.md | 6 +-- ...c-alias-captured-mutate-arr-iife.expect.md | 6 +-- ...g-func-alias-captured-mutate-arr.expect.md | 6 +-- ...-func-alias-captured-mutate-iife.expect.md | 6 +-- ...uring-func-alias-captured-mutate.expect.md | 6 +-- ...turing-function-member-expr-call.expect.md | 6 +-- .../fixtures/compiler/component.expect.md | 12 ++--- .../compiler/computed-call-spread.expect.md | 8 +-- .../compiler/dependencies-outputs.expect.md | 6 +-- .../destructure-in-branch-ssa.expect.md | 8 +-- ...g-same-property-identifier-names.expect.md | 6 +-- .../fixtures/compiler/destructuring.expect.md | 34 ++++++------- ...er-declaration-of-previous-scope.expect.md | 10 ++-- .../existing-variables-with-c-name.expect.md | 6 +-- .../compiler/fast-refresh-reloading.expect.md | 6 +-- ...-mutable-range-destructured-prop.expect.md | 6 +-- ...btparam-with-jsx-element-content.expect.md | 6 +-- ...oop-with-value-block-initializer.expect.md | 6 +-- ...onmutating-loop-local-collection.expect.md | 6 +-- ...pression-prototype-call-mutating.expect.md | 6 +-- ...unctionexpr-conditional-access-2.expect.md | 6 +-- .../functionexpr–conditional-access.expect.md | 6 +-- ...bals-dont-resolve-local-useState.expect.md | 6 +-- .../fixtures/compiler/hook-noAlias.expect.md | 6 +-- .../compiler/hooks-with-prefix.expect.md | 6 +-- .../compiler/import-as-local.expect.md | 2 +- ...incompatible-destructuring-kinds.expect.md | 14 ++--- ...-promoted-to-outer-scope-dynamic.expect.md | 20 ++++---- ...jsx-outlining-child-stored-in-id.expect.md | 12 ++--- .../jsx-outlining-jsx-stored-in-id.expect.md | 18 +++---- .../jsx-outlining-separate-nested.expect.md | 22 ++++---- .../compiler/jsx-outlining-simple.expect.md | 18 +++---- ...-tag-evaluation-order-non-global.expect.md | 16 +++--- .../lambda-capture-returned-alias.expect.md | 6 +-- .../lower-context-acess-multiple.expect.md | 6 +-- .../lower-context-selector-simple.expect.md | 6 +-- ...ge-consecutive-scopes-reordering.expect.md | 6 +-- .../compiler/method-call-computed.expect.md | 8 +-- .../compiler/method-call-fn-call.expect.md | 8 +-- .../fixtures/compiler/method-call.expect.md | 6 +-- .../compiler/module-scoped-bindings.expect.md | 2 +- ...pture-in-unsplittable-memo-block.expect.md | 10 ++-- .../object-shorthand-method-nested.expect.md | 6 +-- ...al-member-expression-as-memo-dep.expect.md | 6 +-- ...ession-single-with-unconditional.expect.md | 6 +-- ...ptional-member-expression-single.expect.md | 6 +-- ...ession-with-conditional-optional.expect.md | 12 ++--- ...mber-expression-with-conditional.expect.md | 12 ++--- ...rly-return-within-reactive-scope.expect.md | 10 ++-- ...Callback-in-other-reactive-block.expect.md | 8 +-- ...k-reordering-deplist-controlflow.expect.md | 16 +++--- ...useMemo-conditional-access-alloc.expect.md | 6 +-- ...eMemo-conditional-access-noAlloc.expect.md | 6 +-- .../useMemo-in-other-reactive-block.expect.md | 8 +-- ...-reordering-depslist-controlflow.expect.md | 6 +-- .../primitive-as-dep-nested-scope.expect.md | 6 +-- ...signed-loop-force-scopes-enabled.expect.md | 8 +-- ...rly-return-within-reactive-scope.expect.md | 8 +-- ...rly-return-within-reactive-scope.expect.md | 8 +-- .../iife-return-modified-later-phi.expect.md | 6 +-- ...al-member-expression-as-memo-dep.expect.md | 6 +-- ...ession-single-with-unconditional.expect.md | 6 +-- ...ptional-member-expression-single.expect.md | 6 +-- ...rly-return-within-reactive-scope.expect.md | 18 +++---- ...hi-type-inference-property-store.expect.md | 6 +-- ...r-function-cond-access-local-var.expect.md | 6 +-- ...function-cond-access-not-hoisted.expect.md | 6 +-- ...n-uncond-access-hoists-other-dep.expect.md | 6 +-- ...uncond-optional-hoists-other-dep.expect.md | 12 ++--- .../memberexpr-join-optional-chain2.expect.md | 6 +-- .../promote-uncond.expect.md | 8 +-- ...a-renaming-unconditional-ternary.expect.md | 8 +-- .../switch-non-final-default.expect.md | 16 +++--- .../switch.expect.md | 16 +++--- ...value-modified-in-catch-escaping.expect.md | 6 +-- ...atch-try-value-modified-in-catch.expect.md | 6 +-- .../useMemo-multiple-if-else.expect.md | 16 +++--- ...-analysis-interleaved-reactivity.expect.md | 6 +-- ...ve-via-mutation-of-computed-load.expect.md | 12 ++--- ...ve-via-mutation-of-property-load.expect.md | 6 +-- .../reassignment-separate-scopes.expect.md | 16 +++--- ...eactive-cond-deps-break-in-scope.expect.md | 6 +-- ...active-cond-deps-return-in-scope.expect.md | 16 +++--- ...function-cond-access-not-hoisted.expect.md | 6 +-- .../hoist-deps-diff-ssa-instance.expect.md | 6 +-- .../jump-poisoned/break-in-scope.expect.md | 6 +-- .../loop-break-in-scope.expect.md | 6 +-- ...e-if-nonexhaustive-poisoned-deps.expect.md | 10 ++-- ...-if-nonexhaustive-poisoned-deps1.expect.md | 10 ++-- .../jump-poisoned/return-in-scope.expect.md | 16 +++--- .../return-poisons-outer-scope.expect.md | 10 ++-- ...p-target-within-scope-loop-break.expect.md | 6 +-- ...e-if-exhaustive-nonpoisoned-deps.expect.md | 10 ++-- ...-if-exhaustive-nonpoisoned-deps1.expect.md | 10 ++-- .../memberexpr-join-optional-chain2.expect.md | 6 +-- .../promote-uncond.expect.md | 6 +-- ...duce-if-exhaustive-poisoned-deps.expect.md | 18 +++---- .../subpath-order1.expect.md | 6 +-- .../superpath-order1.expect.md | 6 +-- .../uncond-access-in-mutable-range.expect.md | 6 +-- .../uncond-nonoverlap-descendant.expect.md | 6 +-- .../reordering-across-blocks.expect.md | 6 +-- ...ed-property-load-for-method-call.expect.md | 6 +-- ...uned-scope-leaks-value-via-alias.expect.md | 6 +-- ...invalid-pruned-scope-leaks-value.expect.md | 6 +-- ...o-invalid-reactivity-value-block.expect.md | 6 +-- ...lack-of-phi-types-explicit-types.expect.md | 6 +-- ...ng-memoization-lack-of-phi-types.expect.md | 6 +-- .../repro-no-value-for-temporary.expect.md | 6 +-- ...ro-propagate-type-of-ternary-jsx.expect.md | 8 +-- ...epro-slow-validate-preserve-memo.expect.md | 6 +-- ...erge-overlapping-reactive-scopes.expect.md | 6 +-- ...ble-code-early-return-in-useMemo.expect.md | 6 +-- .../fixtures/compiler/repro.expect.md | 10 ++-- .../rest-param-with-array-pattern.expect.md | 6 +-- .../rest-param-with-identifier.expect.md | 6 +-- ...param-with-object-spread-pattern.expect.md | 6 +-- ...s-dep-and-redeclare-maybe-frozen.expect.md | 14 ++--- ...me-variable-as-dep-and-redeclare.expect.md | 24 ++++----- ...assignment-to-scope-declarations.expect.md | 14 ++--- ...ixed-local-and-scope-declaration.expect.md | 14 ++--- .../switch-non-final-default.expect.md | 16 +++--- .../fixtures/compiler/switch.expect.md | 16 +++--- .../todo.jsx-outlining-children.expect.md | 12 ++--- ...odo.jsx-outlining-duplicate-prop.expect.md | 12 ++--- ...ntext-access-array-destructuring.expect.md | 6 +-- ...text-access-destructure-multiple.expect.md | 6 +-- ...r-context-access-mixed-array-obj.expect.md | 6 +-- ...text-access-nested-destructuring.expect.md | 6 +-- ...wer-context-access-property-load.expect.md | 6 +-- .../try-catch-in-nested-scope.expect.md | 16 +++--- ...value-modified-in-catch-escaping.expect.md | 6 +-- ...atch-try-value-modified-in-catch.expect.md | 6 +-- .../try-catch-with-catch-param.expect.md | 10 ++-- .../compiler/try-catch-with-return.expect.md | 10 ++-- ...type-provider-log-default-import.expect.md | 12 ++--- .../compiler/type-provider-log.expect.md | 12 ++--- ...r-store-capture-namespace-import.expect.md | 20 ++++---- .../type-provider-store-capture.expect.md | 20 ++++---- .../fixtures/compiler/unary-expr.expect.md | 24 ++++----- .../use-operator-call-expression.expect.md | 6 +-- .../use-operator-conditional.expect.md | 6 +-- .../use-operator-method-call.expect.md | 6 +-- .../useEffect-nested-lambdas.expect.md | 6 +-- .../useState-unpruned-dependency.expect.md | 6 +-- 154 files changed, 726 insertions(+), 679 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 297c771254..167db6dede 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -34,6 +34,7 @@ import { ReactiveInstruction, ReactiveScope, ReactiveScopeBlock, + ReactiveScopeDeclaration, ReactiveScopeDependency, ReactiveTerminal, ReactiveValue, @@ -572,7 +573,8 @@ function codegenReactiveScope( const changeExpressions: Array = []; const changeExpressionComments: Array = []; const outputComments: Array = []; - for (const dep of scope.dependencies) { + + for (const dep of [...scope.dependencies].sort(compareScopeDependency)) { const index = cx.nextCacheIndex; changeExpressionComments.push(printDependencyComment(dep)); const comparison = t.binaryExpression( @@ -615,7 +617,10 @@ function codegenReactiveScope( ); } let firstOutputIndex: number | null = null; - for (const [, {identifier}] of scope.declarations) { + + for (const [, {identifier}] of [...scope.declarations].sort(([, a], [, b]) => + compareScopeDeclaration(a, b), + )) { const index = cx.nextCacheIndex; if (firstOutputIndex === null) { firstOutputIndex = index; @@ -2566,3 +2571,45 @@ function convertIdentifier(identifier: Identifier): t.Identifier { ); return t.identifier(identifier.name.value); } + +function compareScopeDependency( + a: ReactiveScopeDependency, + b: ReactiveScopeDependency, +): number { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected named identifier for dependency', + loc: a.identifier.loc, + }, + ); + const aName = [ + a.identifier.name.value, + ...a.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`), + ].join('.'); + const bName = [ + b.identifier.name.value, + ...b.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`), + ].join('.'); + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; +} + +function compareScopeDeclaration( + a: ReactiveScopeDeclaration, + b: ReactiveScopeDeclaration, +): number { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected named identifier for declaration', + loc: a.identifier.loc, + }, + ); + const aName = a.identifier.name.value; + const bName = b.identifier.name.value; + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md index cb550b4230..6702b26e92 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md @@ -47,7 +47,7 @@ import { identity, mutate, setProperty } from "shared-runtime"; function AllocatingPrimitiveAsDepNested(props) { const $ = _c(5); let t0; - if ($[0] !== props.b || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.b) { const x = {}; mutate(x); const t1 = identity(props.b) + 1; @@ -62,8 +62,8 @@ function AllocatingPrimitiveAsDepNested(props) { const y = t2; setProperty(x, props.a); t0 = [x, y]; - $[0] = props.b; - $[1] = props.a; + $[0] = props.a; + $[1] = props.b; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md index 3aa51ba6d6..a8bad51215 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md @@ -41,7 +41,7 @@ function ArrayAtTest(props) { } const arr = t1; let t2; - if ($[4] !== props.y || $[5] !== arr) { + if ($[4] !== arr || $[5] !== props.y) { let t3; if ($[7] !== props.y) { t3 = bar(props.y); @@ -51,8 +51,8 @@ function ArrayAtTest(props) { t3 = $[8]; } t2 = arr.at(t3); - $[4] = props.y; - $[5] = arr; + $[4] = arr; + $[5] = props.y; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md index 46ae949238..f3af7efcf6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md @@ -22,10 +22,10 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.foo || $[1] !== props.bar) { + if ($[0] !== props.bar || $[1] !== props.foo) { t0 = [0, ...props.foo, null, ...props.bar, "z"]; - $[0] = props.foo; - $[1] = props.bar; + $[0] = props.bar; + $[1] = props.foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md index 7aa47c5803..6618be4a6c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md @@ -24,18 +24,18 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(11); - let t0; let a; + let t0; if ($[0] !== props.a || $[1] !== props.b) { a = [props.a, props.b, "hello"]; t0 = a.push(42); $[0] = props.a; $[1] = props.b; - $[2] = t0; - $[3] = a; + $[2] = a; + $[3] = t0; } else { - t0 = $[2]; - a = $[3]; + a = $[2]; + t0 = $[3]; } const x = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md index 32e2c9fd64..09d2d8800b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md @@ -70,10 +70,10 @@ function Component() { throw new Error("invariant broken"); } let t1; - if ($[1] !== obj || $[2] !== boxedInner) { + if ($[1] !== boxedInner || $[2] !== obj) { t1 = ; - $[1] = obj; - $[2] = boxedInner; + $[1] = boxedInner; + $[2] = obj; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md index 65ab9c277c..5e0b32709b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md @@ -32,7 +32,7 @@ import { mutate } from "shared-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md index 170f68bade..f9ce3f2e98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md index e315cd401d..81737a1ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md index 7371f3d27a..38590d1559 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md index cc41adcbbc..4882aa822f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md index 34f6a55740..7c94c33e49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md index b2d7048756..60493dd25a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..14532562fb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md index 51679299fe..cab9c9a500 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md @@ -46,10 +46,10 @@ function component(t0) { } const hide = t2; let t3; - if ($[4] !== poke || $[5] !== hide) { + if ($[4] !== hide || $[5] !== poke) { t3 = ; - $[4] = poke; - $[5] = hide; + $[4] = hide; + $[5] = poke; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md index 80d6e6df8c..c6037fd2bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md @@ -46,7 +46,7 @@ function Component(props) { const items = props.items; const maxItems = props.maxItems; let renderedItems; - if ($[0] !== maxItems || $[1] !== items) { + if ($[0] !== items || $[1] !== maxItems) { renderedItems = []; const seen = new Set(); const max = Math.max(0, maxItems); @@ -62,8 +62,8 @@ function Component(props) { break; } } - $[0] = maxItems; - $[1] = items; + $[0] = items; + $[1] = maxItems; $[2] = renderedItems; } else { renderedItems = $[2]; @@ -79,15 +79,15 @@ function Component(props) { t0 = $[4]; } let t1; - if ($[5] !== t0 || $[6] !== renderedItems) { + if ($[5] !== renderedItems || $[6] !== t0) { t1 = (
{t0} {renderedItems}
); - $[5] = t0; - $[6] = renderedItems; + $[5] = renderedItems; + $[6] = t0; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md index cb20d97cb7..0329450b13 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md @@ -16,11 +16,11 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.method || $[1] !== props.a || $[2] !== props.b) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.method) { t0 = foo[props.method](...props.a, null, ...props.b); - $[0] = props.method; - $[1] = props.a; - $[2] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.method; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md index 6fc686cb19..f0f9911c07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md @@ -41,7 +41,7 @@ function foo(a, b) { x = $[1]; } let y; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { y = []; if (x.length) { y.push(x); @@ -49,8 +49,8 @@ function foo(a, b) { if (b) { y.push(b); } - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = y; } else { y = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md index d65082cbc8..b159789106 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md @@ -61,11 +61,11 @@ function useFoo(props) { z = $[4]; } let t0; - if ($[5] !== x || $[6] !== y || $[7] !== myList) { + if ($[5] !== myList || $[6] !== x || $[7] !== y) { t0 = { x, y, myList }; - $[5] = x; - $[6] = y; - $[7] = myList; + $[5] = myList; + $[6] = x; + $[7] = y; $[8] = t0; } else { t0 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md index b86498b922..3e1c4771ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md @@ -41,10 +41,10 @@ function Component(props) { } const sameName = t1; let t2; - if ($[2] !== sameName || $[3] !== renamed) { + if ($[2] !== renamed || $[3] !== sameName) { t2 = [sameName, renamed]; - $[2] = sameName; - $[3] = renamed; + $[2] = renamed; + $[3] = sameName; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md index c175cc558c..f292e83e16 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md @@ -36,45 +36,45 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(18); - let t0; let d; let h; + let t0; if ($[0] !== a) { [d, t0, ...h] = a; $[0] = a; - $[1] = t0; - $[2] = d; - $[3] = h; + $[1] = d; + $[2] = h; + $[3] = t0; } else { - t0 = $[1]; - d = $[2]; - h = $[3]; + d = $[1]; + h = $[2]; + t0 = $[3]; } const [t1] = t0; - let t2; let g; + let t2; if ($[4] !== t1) { ({ e: t2, ...g } = t1); $[4] = t1; - $[5] = t2; - $[6] = g; + $[5] = g; + $[6] = t2; } else { - t2 = $[5]; - g = $[6]; + g = $[5]; + t2 = $[6]; } const { f } = t2; const { l: t3, p } = b; const { m: t4 } = t3; - let t5; let o; + let t5; if ($[7] !== t4) { [t5, ...o] = t4; $[7] = t4; - $[8] = t5; - $[9] = o; + $[8] = o; + $[9] = t5; } else { - t5 = $[8]; - o = $[9]; + o = $[8]; + t5 = $[9]; } const [n] = t5; let t6; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md index 29780eb76c..ce5bfda644 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md @@ -53,8 +53,8 @@ import { ValidateMemoization } from "shared-runtime"; function Component(t0) { const $ = _c(25); const { a, b, c } = t0; - let y; let x; + let y; if ($[0] !== a || $[1] !== b || $[2] !== c) { x = []; if (a) { @@ -73,11 +73,11 @@ function Component(t0) { $[0] = a; $[1] = b; $[2] = c; - $[3] = y; - $[4] = x; + $[3] = x; + $[4] = y; } else { - y = $[3]; - x = $[4]; + x = $[3]; + y = $[4]; } let t1; if ($[7] !== y) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md index 0d671a3de2..5cde3bde23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md @@ -61,10 +61,10 @@ function Component(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== array) { + if ($[4] !== array || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = array; + $[4] = array; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md index ecd03a0b10..4175d23fda 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md @@ -59,10 +59,10 @@ function Component(props) { t3 = $[4]; } let t4; - if ($[5] !== t3 || $[6] !== doubled) { + if ($[5] !== doubled || $[6] !== t3) { t4 = ; - $[5] = t3; - $[6] = doubled; + $[5] = doubled; + $[6] = t3; $[7] = t4; } else { t4 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md index d56f7a98ad..9bb651aa67 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md @@ -61,10 +61,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== el) { + if ($[4] !== el || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = el; + $[4] = el; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md index d58c25b510..56ffb70cb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md @@ -32,7 +32,7 @@ function Component(t0) { const $ = _c(4); const { name, data, icon } = t0; let t1; - if ($[0] !== name || $[1] !== icon || $[2] !== data) { + if ($[0] !== data || $[1] !== icon || $[2] !== name) { t1 = ( {fbt._( @@ -61,9 +61,9 @@ function Component(t0) { )} ); - $[0] = name; + $[0] = data; $[1] = icon; - $[2] = data; + $[2] = name; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md index 24f2e545cc..6ef73de8b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md @@ -67,7 +67,7 @@ const TOTAL = 10; function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.start || $[1] !== props.items) { + if ($[0] !== props.items || $[1] !== props.start) { const items = []; for (let i = props.start ?? 0; i < props.items.length; i++) { const item = props.items[i]; @@ -75,8 +75,8 @@ function Component(props) { } t0 =
{items}
; - $[0] = props.start; - $[1] = props.items; + $[0] = props.items; + $[1] = props.start; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md index cff8c5bd13..4abe630044 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md @@ -91,10 +91,10 @@ function Component(t0) { t5 = $[9]; } let t6; - if ($[10] !== x || $[11] !== b) { + if ($[10] !== b || $[11] !== x) { t6 = [x, b]; - $[10] = x; - $[11] = b; + $[10] = b; + $[11] = x; $[12] = t6; } else { t6 = $[12]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md index be59673c15..9888e96222 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md @@ -59,10 +59,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== a_0) { + if ($[4] !== a_0 || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = a_0; + $[4] = a_0; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..32498e1379 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -36,10 +36,10 @@ function Component(t0) { } const f = t1; let t2; - if ($[2] !== props || $[3] !== f) { + if ($[2] !== f || $[3] !== props) { t2 = props == null ? _temp : f; - $[2] = props; - $[3] = f; + $[2] = f; + $[3] = props; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..4a62bf6f24 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -36,10 +36,10 @@ function Component(props) { } const getLength = t0; let t1; - if ($[2] !== props.bar || $[3] !== getLength) { + if ($[2] !== getLength || $[3] !== props.bar) { t1 = props.bar && getLength(); - $[2] = props.bar; - $[3] = getLength; + $[2] = getLength; + $[3] = props.bar; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md index 7548a3b639..be7f3f1bd2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md @@ -56,10 +56,10 @@ function Component() { t0 = $[1]; } let t1; - if ($[2] !== t0 || $[3] !== state) { + if ($[2] !== state || $[3] !== t0) { t1 =
{state}
; - $[2] = t0; - $[3] = state; + $[2] = state; + $[3] = t0; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md index 96ccd1e2f1..329d57a035 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md @@ -41,10 +41,10 @@ function Component(props) { console.log(props); }, [props.a]); let t1; - if ($[2] !== x || $[3] !== item) { + if ($[2] !== item || $[3] !== x) { t1 = [x, item]; - $[2] = x; - $[3] = item; + $[2] = item; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md index f7e02a53f1..085df625f5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md @@ -73,15 +73,15 @@ function Component() { t1 = $[4]; } let t3; - if ($[5] !== t2 || $[6] !== json) { + if ($[5] !== json || $[6] !== t2) { t3 = (
{t2} {json}
); - $[5] = t2; - $[6] = json; + $[5] = json; + $[6] = t2; $[7] = t3; } else { t3 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/import-as-local.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/import-as-local.expect.md index afbb1bdfe7..fea502d0d3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/import-as-local.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/import-as-local.expect.md @@ -137,4 +137,4 @@ export const FIXTURE_ENTRYPOINT = { ``` ### Eval output -(kind: ok)
Hello
\ No newline at end of file +(kind: exception) (0 , _react.experimental_useEffectEvent) is not a function \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md index 970cc50f03..8afc59a80b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md @@ -29,22 +29,22 @@ import { Stringify } from "shared-runtime"; function Component(t0) { const $ = _c(4); - let t1; let a; let b; + let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { a = "a"; const [t2, t3] = [null, null]; t1 = t3; a = t2; - $[0] = t1; - $[1] = a; - $[2] = b; + $[0] = a; + $[1] = b; + $[2] = t1; } else { - t1 = $[0]; - a = $[1]; - b = $[2]; + a = $[0]; + b = $[1]; + t1 = $[2]; } b = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md index becc4066e7..735462657f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md @@ -27,10 +27,10 @@ function Component(props) { const $ = _c(15); const item = useFragment(FRAGMENT, props.item); useFreeze(item); - let t0; let T0; - let t1; let T1; + let t0; + let t1; if ($[0] !== item) { const count = new MaybeMutable(item); @@ -44,15 +44,15 @@ function Component(props) { } t0 = maybeMutate(count); $[0] = item; - $[1] = t0; - $[2] = T0; - $[3] = t1; - $[4] = T1; + $[1] = T0; + $[2] = T1; + $[3] = t0; + $[4] = t1; } else { - t0 = $[1]; - T0 = $[2]; - t1 = $[3]; - T1 = $[4]; + T0 = $[1]; + T1 = $[2]; + t0 = $[3]; + t1 = $[4]; } let t2; if ($[6] !== t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..b84229156b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -83,10 +83,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -98,15 +98,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md index 496282e1ef..7fca963134 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md @@ -52,7 +52,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -66,8 +66,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -94,10 +94,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -109,15 +109,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md index 7f86546cd4..9d2b254c06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md @@ -60,7 +60,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -73,8 +73,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -117,7 +117,7 @@ function _temp(t0) { t3 = $[5]; } let t4; - if ($[6] !== x || $[7] !== t1 || $[8] !== t2 || $[9] !== t3) { + if ($[6] !== t1 || $[7] !== t2 || $[8] !== t3 || $[9] !== x) { t4 = ( {t1} @@ -125,10 +125,10 @@ function _temp(t0) { {t3} ); - $[6] = x; - $[7] = t1; - $[8] = t2; - $[9] = t3; + $[6] = t1; + $[7] = t2; + $[8] = t3; + $[9] = x; $[10] = t4; } else { t4 = $[10]; @@ -140,15 +140,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md index 9e268227a2..09323f5ac6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -63,8 +63,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,10 +91,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -106,15 +106,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md index b1dfbc61c3..d46ce53bb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md @@ -53,23 +53,23 @@ function maybeMutate(x) {} function Component(props) { const $ = _c(11); - let Tag; let T0; + let Tag; let t0; - if ($[0] !== props.component || $[1] !== props.alternateComponent) { + if ($[0] !== props.alternateComponent || $[1] !== props.component) { const maybeMutable = new MaybeMutable(); Tag = props.component; T0 = Tag; t0 = ((Tag = props.alternateComponent), maybeMutate(maybeMutable)); - $[0] = props.component; - $[1] = props.alternateComponent; - $[2] = Tag; - $[3] = T0; + $[0] = props.alternateComponent; + $[1] = props.component; + $[2] = T0; + $[3] = Tag; $[4] = t0; } else { - Tag = $[2]; - T0 = $[3]; + T0 = $[2]; + Tag = $[3]; t0 = $[4]; } let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md index e172ee1039..bac21217c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md @@ -44,7 +44,7 @@ function CaptureNotMutate(props) { } const idx = t0; let aliasedElement; - if ($[2] !== props.el || $[3] !== idx) { + if ($[2] !== idx || $[3] !== props.el) { const element = bar(props.el); const fn = function () { @@ -54,8 +54,8 @@ function CaptureNotMutate(props) { aliasedElement = fn(); mutate(aliasedElement); - $[2] = props.el; - $[3] = idx; + $[2] = idx; + $[3] = props.el; $[4] = aliasedElement; } else { aliasedElement = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md index 47c7a2d743..af9b1df36a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md @@ -21,10 +21,10 @@ function App() { const { foo } = useContext_withSelector(MyContext, _temp); const { bar } = useContext_withSelector(MyContext, _temp2); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md index 0b12c2e250..d13682467b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md @@ -19,10 +19,10 @@ function App() { const $ = _c(3); const { foo, bar } = useContext_withSelector(MyContext, _temp); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md index 5bf1f2cf4d..e5a9081137 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md @@ -60,7 +60,7 @@ function Component() { t2 = $[3]; } let t3; - if ($[4] !== t1 || $[5] !== t0) { + if ($[4] !== t0 || $[5] !== t1) { t3 = (
{t2} @@ -68,8 +68,8 @@ function Component() { {t0}
); - $[4] = t1; - $[5] = t0; + $[4] = t0; + $[5] = t1; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md index 887479c01e..2fc302c8b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md @@ -43,11 +43,11 @@ function foo(a, b, c) { } const y = t1; let t2; - if ($[4] !== x || $[5] !== y.method || $[6] !== b) { + if ($[4] !== b || $[5] !== x || $[6] !== y.method) { t2 = x[y.method](b); - $[4] = x; - $[5] = y.method; - $[6] = b; + $[4] = b; + $[5] = x; + $[6] = y.method; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md index c32777534b..58c06101d3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md @@ -33,11 +33,11 @@ function foo(a, b, c) { const method = x.method; let t1; - if ($[2] !== method || $[3] !== x || $[4] !== b) { + if ($[2] !== b || $[3] !== method || $[4] !== x) { t1 = method.call(x, b); - $[2] = method; - $[3] = x; - $[4] = b; + $[2] = b; + $[3] = method; + $[4] = x; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md index ad45287d1f..fd8a4935a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md @@ -40,10 +40,10 @@ function foo(a, b, c) { } const x = t0; let t1; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { t1 = x.foo(b); - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/module-scoped-bindings.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/module-scoped-bindings.expect.md index a47554cfca..5c870b43af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/module-scoped-bindings.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/module-scoped-bindings.expect.md @@ -101,4 +101,4 @@ export const FIXTURE_ENTRYPOINT = { ``` ### Eval output -(kind: ok) [{"Children":{"map":"[[ function params=3 ]]","forEach":"[[ function params=3 ]]","count":"[[ function params=1 ]]","toArray":"[[ function params=1 ]]","only":"[[ function params=1 ]]"},"Component":"[[ function params=3 ]]","PureComponent":"[[ function params=3 ]]","__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE":{"H":{"readContext":"[[ function params=1 ]]","use":"[[ function params=1 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useEffect":"[[ function params=2 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useDebugValue":"[[ function params=0 ]]","useDeferredValue":"[[ function params=2 ]]","useTransition":"[[ function params=0 ]]","useSyncExternalStore":"[[ function params=3 ]]","useId":"[[ function params=0 ]]","useCacheRefresh":"[[ function params=0 ]]","useMemoCache":"[[ function params=1 ]]","useEffectEvent":"[[ function params=1 ]]","useHostTransitionStatus":"[[ function params=0 ]]","useFormState":"[[ function params=2 ]]","useActionState":"[[ function params=2 ]]","useOptimistic":"[[ function params=1 ]]"},"A":{"getCacheForType":"[[ function params=1 ]]","getOwner":"[[ function params=0 ]]"},"T":null,"S":"[[ function params=2 ]]","actQueue":["[[ function params=0 ]]","[[ function params=1 ]]"],"isBatchingLegacy":false,"didScheduleLegacyUpdate":false,"didUsePromise":false,"thrownErrors":[],"getCurrentStack":"[[ function params=0 ]]"},"__COMPILER_RUNTIME":{"c":"[[ function params=1 ]]"},"act":"[[ function params=1 ]]","cache":"[[ function params=1 ]]","captureOwnerStack":"[[ function params=0 ]]","cloneElement":"[[ function params=3 ]]","createContext":"[[ function params=1 ]]","createElement":"[[ function params=3 ]]","createRef":"[[ function params=0 ]]","experimental_useEffectEvent":"[[ function params=1 ]]","experimental_useOptimistic":"[[ function params=2 ]]","forwardRef":"[[ function params=1 ]]","isValidElement":"[[ function params=1 ]]","lazy":"[[ function params=1 ]]","memo":"[[ function params=2 ]]","startTransition":"[[ function params=1 ]]","unstable_getCacheForType":"[[ function params=1 ]]","unstable_postpone":"[[ function params=1 ]]","unstable_useCacheRefresh":"[[ function params=0 ]]","use":"[[ function params=1 ]]","useActionState":"[[ function params=3 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useDebugValue":"[[ function params=2 ]]","useDeferredValue":"[[ function params=2 ]]","useEffect":"[[ function params=2 ]]","useId":"[[ function params=0 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useOptimistic":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useSyncExternalStore":"[[ function params=3 ]]","useTransition":"[[ function params=0 ]]","version":"19.0.0-experimental-0bc30748-20241028"},"[[ cyclic ref *6 ]]",true,true,true,true,"[[ function params=0 ]]",true,"[[ function params=0 ]]"] \ No newline at end of file +(kind: ok) [{"Children":{"map":"[[ function params=3 ]]","forEach":"[[ function params=3 ]]","count":"[[ function params=1 ]]","toArray":"[[ function params=1 ]]","only":"[[ function params=1 ]]"},"Component":"[[ function params=3 ]]","PureComponent":"[[ function params=3 ]]","__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE":{"H":{"readContext":"[[ function params=1 ]]","use":"[[ function params=1 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useEffect":"[[ function params=2 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useDebugValue":"[[ function params=2 ]]","useDeferredValue":"[[ function params=2 ]]","useTransition":"[[ function params=0 ]]","useSyncExternalStore":"[[ function params=3 ]]","useId":"[[ function params=0 ]]","useCacheRefresh":"[[ function params=0 ]]","useMemoCache":"[[ function params=1 ]]","useHostTransitionStatus":"[[ function params=0 ]]","useFormState":"[[ function params=3 ]]","useActionState":"[[ function params=3 ]]","useOptimistic":"[[ function params=2 ]]"},"A":{"getCacheForType":"[[ function params=1 ]]","getOwner":"[[ function params=0 ]]"},"T":null,"actQueue":["[[ function params=0 ]]","[[ function params=1 ]]"],"isBatchingLegacy":false,"didScheduleLegacyUpdate":false,"didUsePromise":false,"thrownErrors":[],"setExtraStackFrame":"[[ function params=1 ]]","getCurrentStack":"[[ function params=0 ]]","getStackAddendum":"[[ function params=0 ]]"},"act":"[[ function params=1 ]]","cache":"[[ function params=1 ]]","cloneElement":"[[ function params=3 ]]","createContext":"[[ function params=1 ]]","createElement":"[[ function params=3 ]]","createRef":"[[ function params=0 ]]","forwardRef":"[[ function params=1 ]]","isValidElement":"[[ function params=1 ]]","lazy":"[[ function params=1 ]]","memo":"[[ function params=2 ]]","startTransition":"[[ function params=2 ]]","unstable_useCacheRefresh":"[[ function params=0 ]]","use":"[[ function params=1 ]]","useActionState":"[[ function params=3 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useDebugValue":"[[ function params=2 ]]","useDeferredValue":"[[ function params=2 ]]","useEffect":"[[ function params=2 ]]","useId":"[[ function params=0 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useOptimistic":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useSyncExternalStore":"[[ function params=3 ]]","useTransition":"[[ function params=0 ]]","version":"19.0.0-beta-b498834eab-20240506"},"[[ cyclic ref *6 ]]",true,true,true,true,"[[ function params=0 ]]",true,"[[ function params=0 ]]"] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md index 090e9d889c..a3403e6c8d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md @@ -73,8 +73,8 @@ import { identity, mutate } from "shared-runtime"; function useFoo(t0) { const $ = _c(4); const { a, b } = t0; - let z; let y; + let z; if ($[0] !== a || $[1] !== b) { const x = { a }; y = {}; @@ -83,11 +83,11 @@ function useFoo(t0) { mutate(y); $[0] = a; $[1] = b; - $[2] = z; - $[3] = y; + $[2] = y; + $[3] = z; } else { - z = $[2]; - y = $[3]; + y = $[2]; + z = $[3]; } if (z[0] !== y) { throw new Error("oh no!"); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md index 4b500f52d6..dc1cd699d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md @@ -40,7 +40,7 @@ function useHook(t0) { const { value } = t0; const [state] = useState(false); let t1; - if ($[0] !== value || $[1] !== state) { + if ($[0] !== state || $[1] !== value) { t1 = { getX() { return { @@ -52,8 +52,8 @@ function useHook(t0) { }; }, }; - $[0] = value; - $[1] = state; + $[0] = state; + $[1] = value; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md index 77875f789d..3dd8e73032 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md index 46767056bd..3cd9877813 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md index 6e44a97b45..60a6171ab1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md index 77ded20d93..2674d78c99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md index 10c23085d8..1d4a50d285 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..42b3b21a20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); - let y; let t0; + let y; if ($[0] !== props) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -57,11 +57,11 @@ function Component(props) { } } $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md index 9ca63e23c4..3da133e929 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(7); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; if ($[4] !== minWidth || $[5] !== width) { @@ -55,9 +55,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md index 947e3bd2eb..ee4e4634cb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md @@ -42,9 +42,9 @@ import { Stringify } from "shared-runtime"; function Foo(t0) { const $ = _c(8); const { arr1, arr2, foo } = t0; - let t1; let getVal1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + let t1; + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -55,13 +55,13 @@ function Foo(t0) { t1 = () => [y]; foo ? (y = x.concat(arr2)) : y; $[0] = arr1; - $[1] = foo; - $[2] = arr2; - $[3] = t1; - $[4] = getVal1; + $[1] = arr2; + $[2] = foo; + $[3] = getVal1; + $[4] = t1; } else { - t1 = $[3]; - getVal1 = $[4]; + getVal1 = $[3]; + t1 = $[4]; } const getVal2 = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md index 3a7016f803..f7353ddd5e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md @@ -44,10 +44,10 @@ function Component(t0) { t3 = $[1]; } let t4; - if ($[2] !== t3 || $[3] !== propA) { + if ($[2] !== propA || $[3] !== t3) { t4 = { value: t3, other: propA }; - $[2] = t3; - $[3] = propA; + $[2] = propA; + $[3] = t3; $[4] = t4; } else { t4 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md index f0ce9b9114..c6ad9bcdac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md @@ -34,10 +34,10 @@ function Component(t0) { const t2 = propB?.x.y; let t3; - if ($[0] !== t2 || $[1] !== propA) { + if ($[0] !== propA || $[1] !== t2) { t3 = { value: t2, other: propA }; - $[0] = t2; - $[1] = propA; + $[0] = propA; + $[1] = t2; $[2] = t3; } else { t3 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md index 9d7feacd6d..7a27bb8521 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(6); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; @@ -58,9 +58,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md index e9d2bffb30..5fc0ec510b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md @@ -44,7 +44,7 @@ function Foo(t0) { const { arr1, arr2, foo } = t0; let t1; let val1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -63,8 +63,8 @@ function Foo(t0) { foo ? (y = x.concat(arr2)) : y; t1 = (() => [y])(); $[0] = arr1; - $[1] = foo; - $[2] = arr2; + $[1] = arr2; + $[2] = foo; $[3] = t1; $[4] = val1; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md index e1d4bb5a8a..580a97ac19 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md @@ -49,7 +49,7 @@ import { identity, mutate, setProperty } from "shared-runtime"; function PrimitiveAsDepNested(props) { const $ = _c(5); let t0; - if ($[0] !== props.b || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.b) { const x = {}; mutate(x); const t1 = props.b + 1; @@ -64,8 +64,8 @@ function PrimitiveAsDepNested(props) { const y = t2; setProperty(x, props.a); t0 = [x, y]; - $[0] = props.b; - $[1] = props.a; + $[0] = props.a; + $[1] = props.b; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md index 594e68f24f..de39fc9706 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md @@ -32,15 +32,15 @@ function Component(t0) { const $ = _c(5); const { base, start, increment, test } = t0; let value; - if ($[0] !== base || $[1] !== start || $[2] !== test || $[3] !== increment) { + if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) { value = base; for (let i = start; i < test; i = i + increment, i) { value = value + i; } $[0] = base; - $[1] = start; - $[2] = test; - $[3] = increment; + $[1] = increment; + $[2] = start; + $[3] = test; $[4] = value; } else { value = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md index 476e1c017c..c235681d38 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md @@ -34,7 +34,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(7); let t0; - if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -69,9 +69,9 @@ function Component(props) { break bb0; } } - $[0] = props.cond; - $[1] = props.a; - $[2] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md index bf54b52b59..0e93d32061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md @@ -48,7 +48,7 @@ import { makeArray } from "shared-runtime"; function Component(props) { const $ = _c(6); let t0; - if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -69,9 +69,9 @@ function Component(props) { break bb0; } } - $[0] = props.cond; - $[1] = props.a; - $[2] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md index 744824d0bd..2a3da8b195 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md @@ -29,7 +29,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(3); let items; - if ($[0] !== props.cond || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.cond) { let t0; if (props.cond) { t0 = []; @@ -39,8 +39,8 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props.cond; - $[1] = props.a; + $[0] = props.a; + $[1] = props.cond; $[2] = items; } else { items = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md index d0486cd8c2..28612f2d73 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md index b4a55fcb61..2861ab71c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md index f15b9b8e9b..b5db44aa2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md index 324eb714fc..1972e92237 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md @@ -32,9 +32,9 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(6); - let y; let t0; - if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { + let y; + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,14 +57,14 @@ function Component(props) { } } } - $[0] = props.cond; - $[1] = props.a; - $[2] = props.b; - $[3] = y; - $[4] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md index 1bfaeb1c84..6c7a91ba33 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props.cond || $[2] !== props.a) { + if ($[1] !== props.a || $[2] !== props.cond) { let y; if (props.cond) { y = {}; @@ -53,8 +53,8 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props.cond; - $[2] = props.a; + $[1] = props.a; + $[2] = props.cond; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md index c0f8aa97cd..673dd0d5fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md @@ -58,7 +58,7 @@ function useFoo(t0) { local = $[1]; } let t1; - if ($[2] !== shouldReadA || $[3] !== local) { + if ($[2] !== local || $[3] !== shouldReadA) { t1 = ( { @@ -70,8 +70,8 @@ function useFoo(t0) { shouldInvokeFns={true} /> ); - $[2] = shouldReadA; - $[3] = local; + $[2] = local; + $[3] = shouldReadA; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md index e37b8365a2..abf4c98f23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md @@ -41,7 +41,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a) { + if ($[0] !== a || $[1] !== shouldReadA) { t1 = ( { @@ -53,8 +53,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a; + $[0] = a; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md index 89b4d281f8..d82956e4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md @@ -51,13 +51,13 @@ function Foo(t0) { const fn = t1; useIdentity(null); let x; - if ($[2] !== cond || $[3] !== a.b.c) { + if ($[2] !== a.b.c || $[3] !== cond) { x = makeArray(); if (cond) { x.push(identity(a.b.c)); } - $[2] = cond; - $[3] = a.b.c; + $[2] = a.b.c; + $[3] = cond; $[4] = x; } else { x = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md index 591e04de7b..c81e59ecea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md @@ -50,22 +50,22 @@ function Foo(t0) { const fn = t1; useIdentity(null); let arr; - if ($[2] !== cond || $[3] !== a.b?.c.e) { + if ($[2] !== a.b?.c.e || $[3] !== cond) { arr = makeArray(); if (cond) { arr.push(identity(a.b?.c.e)); } - $[2] = cond; - $[3] = a.b?.c.e; + $[2] = a.b?.c.e; + $[3] = cond; $[4] = arr; } else { arr = $[4]; } let t2; - if ($[5] !== fn || $[6] !== arr) { + if ($[5] !== arr || $[6] !== fn) { t2 = ; - $[5] = fn; - $[6] = arr; + $[5] = arr; + $[6] = fn; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md index df9dec4fb6..277c203525 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(5); let x; - if ($[0] !== props.items?.length || $[1] !== props.items?.edges) { + if ($[0] !== props.items?.edges || $[1] !== props.items?.length) { x = []; x.push(props.items?.length); let t0; @@ -36,8 +36,8 @@ function Component(props) { t0 = $[4]; } x.push(t0); - $[0] = props.items?.length; - $[1] = props.items?.edges; + $[0] = props.items?.edges; + $[1] = props.items?.length; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md index 902a1578c8..30cea1e2c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md @@ -38,15 +38,15 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(4); let x; - if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { + if ($[0] !== other || $[1] !== props.a.a.a || $[2] !== props.a.b) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a.a.a; - $[1] = props.a.b; - $[2] = other; + $[0] = other; + $[1] = props.a.a.a; + $[2] = props.a.b; $[3] = x; } else { x = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md index c5cf366fb6..923830fb4b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md @@ -39,11 +39,11 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { + if ($[2] !== props.bar || $[3] !== props.cond || $[4] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props.cond; - $[3] = props.foo; - $[4] = props.bar; + $[2] = props.bar; + $[3] = props.cond; + $[4] = props.foo; $[5] = x; } else { x = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md index 37846215b1..fee31c9faf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md @@ -35,8 +35,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { @@ -65,19 +65,19 @@ function Component(props) { t0 = ; $[0] = props.p0; $[1] = props.p2; - $[2] = y; - $[3] = t0; + $[2] = t0; + $[3] = y; } else { - y = $[2]; - t0 = $[3]; + t0 = $[2]; + y = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md index 1be4143849..6290668015 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md @@ -30,8 +30,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { @@ -48,19 +48,19 @@ function Component(props) { $[0] = props.p0; $[1] = props.p2; $[2] = props.p3; - $[3] = y; - $[4] = t0; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md index f69994b0a8..5cd81990e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md @@ -34,7 +34,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let x; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { try { const y = []; y.push(props.y); @@ -44,8 +44,8 @@ function Component(props) { e.push(props.e); x = e; } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md index bc47228371..dc41af6275 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md @@ -33,7 +33,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { try { @@ -47,8 +47,8 @@ function Component(props) { break bb0; } } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md index d654221dc8..a75b592b83 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md @@ -39,10 +39,10 @@ function Component(props) { bb0: { let y; if ( - $[0] !== props.cond || - $[1] !== props.a || - $[2] !== props.cond2 || - $[3] !== props.b + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.cond || + $[3] !== props.cond2 ) { y = []; if (props.cond) { @@ -54,10 +54,10 @@ function Component(props) { } y.push(props.b); - $[0] = props.cond; - $[1] = props.a; - $[2] = props.cond2; - $[3] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = props.cond2; $[4] = y; $[5] = t0; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md index 7480362a43..c6331bd4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md @@ -54,10 +54,10 @@ function Component(props) { } const c = t0; let t1; - if ($[3] !== c || $[4] !== a) { + if ($[3] !== a || $[4] !== c) { t1 = [c, a]; - $[3] = c; - $[4] = a; + $[3] = a; + $[4] = c; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md index 29e3e2757f..5ac29886cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md @@ -20,11 +20,11 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(8); let items; - if ($[0] !== props.key || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.key) { items = bar(); mutate(items[props.key], props.a); - $[0] = props.key; - $[1] = props.a; + $[0] = props.a; + $[1] = props.key; $[2] = items; } else { items = $[2]; @@ -41,10 +41,10 @@ function Component(props) { } const count = t1; let t2; - if ($[5] !== items || $[6] !== count) { + if ($[5] !== count || $[6] !== items) { t2 = { items, count }; - $[5] = items; - $[6] = count; + $[5] = count; + $[6] = items; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md index 3408f707d6..bb76df4de0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md @@ -40,10 +40,10 @@ function Component(props) { } const count = t1; let t2; - if ($[4] !== items || $[5] !== count) { + if ($[4] !== count || $[5] !== items) { t2 = { items, count }; - $[4] = items; - $[5] = count; + $[4] = count; + $[5] = items; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md index e682a01086..422f4cf547 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md @@ -42,8 +42,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(10); - let x; let t0; + let x; if ($[0] !== a) { x = []; if (a) { @@ -52,11 +52,11 @@ function foo(a, b, c) { t0 =
{x}
; $[0] = a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const y = t0; bb0: switch (b) { @@ -83,15 +83,15 @@ function foo(a, b, c) { } } let t1; - if ($[7] !== y || $[8] !== x) { + if ($[7] !== x || $[8] !== y) { t1 = (
{y} {x}
); - $[7] = y; - $[8] = x; + $[7] = x; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md index 3ad544344d..f3ddf57ec0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md @@ -34,7 +34,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md index 449aa6d3d8..5700634449 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md @@ -31,9 +31,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..18e9faf63b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== a.b.c || $[1] !== shouldReadA) { t1 = ( { @@ -50,8 +50,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a.b.c; + $[0] = a.b.c; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md index 701702f9dd..2dd9362404 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md @@ -80,10 +80,10 @@ function useFoo(t0) { x = $[6]; } let t1; - if ($[7] !== y || $[8] !== x.a.b) { + if ($[7] !== x.a.b || $[8] !== y) { t1 = [y, x.a.b]; - $[7] = y; - $[8] = x.a.b; + $[7] = x.a.b; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md index b4c25d5f45..e024bc893a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md index 359ba0dcde..055d1ce12e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; for (let i = 0; i < 5; i++) { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md index bb47587687..1c2162352b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md @@ -42,8 +42,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -77,11 +77,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md index 59225b5155..ca8228e2db 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md index 7a7f9a4b6e..ce12fb17b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md @@ -33,9 +33,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md index 2b925c2479..058bf85b69 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md @@ -39,8 +39,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { input, cond } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -61,11 +61,11 @@ function useFoo(t0) { } $[0] = cond; $[1] = input; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md index 291998c8ad..2715a0c92d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md @@ -40,7 +40,7 @@ function useFoo(t0) { const $ = _c(3); const { input, max } = t0; let x; - if ($[0] !== max || $[1] !== input.a.b) { + if ($[0] !== input.a.b || $[1] !== max) { x = []; let i = 0; while (true) { @@ -52,8 +52,8 @@ function useFoo(t0) { x.push(i); x.push(input.a.b); - $[0] = max; - $[1] = input.a.b; + $[0] = input.a.b; + $[1] = max; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md index 84b8fa1d43..845ea4b5ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md @@ -33,8 +33,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, hasAB, returnNull } = t0; - let x; let t1; + let x; if ($[0] !== hasAB || $[1] !== input.a || $[2] !== returnNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -68,11 +68,11 @@ function useFoo(t0) { $[0] = hasAB; $[1] = input.a; $[2] = returnNull; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md index a55922f610..d3e463495b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond2, cond1 } = t0; - let x; let t1; + let x; if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== input.a.b) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond1; $[1] = cond2; $[2] = input.a.b; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md index 8d69c008c5..7818ca4e0d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(5); let x; - if ($[0] !== props.items?.length || $[1] !== props.items?.edges) { + if ($[0] !== props.items?.edges || $[1] !== props.items?.length) { x = []; x.push(props.items?.length); let t0; @@ -35,8 +35,8 @@ function Component(props) { t0 = $[4]; } x.push(t0); - $[0] = props.items?.length; - $[1] = props.items?.edges; + $[0] = props.items?.edges; + $[1] = props.items?.length; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..d3a61a1019 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -36,14 +36,14 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== other || $[1] !== props.a) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; + $[0] = other; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md index 01ab4f6b0a..41a7a25d63 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md @@ -34,9 +34,9 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, inputHasAB, inputHasABC } = t0; - let x; let t1; - if ($[0] !== inputHasABC || $[1] !== input.a || $[2] !== inputHasAB) { + let x; + if ($[0] !== input.a || $[1] !== inputHasAB || $[2] !== inputHasABC) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -75,14 +75,14 @@ function useFoo(t0) { x.push(t2); } } - $[0] = inputHasABC; - $[1] = input.a; - $[2] = inputHasAB; - $[3] = x; - $[4] = t1; + $[0] = input.a; + $[1] = inputHasAB; + $[2] = inputHasABC; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md index a62223d72d..03e3e96397 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md @@ -40,14 +40,14 @@ import { identity } from "shared-runtime"; function useConditionalSubpath1(props, cond) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.b = props.a.b; if (identity(cond)) { x.a = props.a; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md index 02117feee2..5b246175f9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md @@ -48,14 +48,14 @@ function useConditionalSuperpath1(t0) { const $ = _c(3); const { props, cond } = t0; let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.a = props.a; if (identity(cond)) { x.b = props.a.b; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md index 34979e9de9..c91cf94445 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md @@ -49,15 +49,15 @@ function Component(t0) { const $ = _c(8); const { cond, other } = t0; let x; - if ($[0] !== other || $[1] !== cond) { + if ($[0] !== cond || $[1] !== other) { x = makeObject_Primitives(); setProperty(x, { b: 3, other }, "a"); identity(x.a.b); if (!cond) { x.a = null; } - $[0] = other; - $[1] = cond; + $[0] = cond; + $[1] = other; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md index 0a1a423361..ff15f1a336 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md @@ -27,13 +27,13 @@ import { c as _c } from "react/compiler-runtime"; // Test that we can track non- function TestNonOverlappingDescendantTracked(props) { const $ = _c(4); let x; - if ($[0] !== props.a.x.y || $[1] !== props.a.c.x.y.z || $[2] !== props.b) { + if ($[0] !== props.a.c.x.y.z || $[1] !== props.a.x.y || $[2] !== props.b) { x = {}; x.a = props.a.x.y; x.b = props.b; x.c = props.a.c.x.y.z; - $[0] = props.a.x.y; - $[1] = props.a.c.x.y.z; + $[0] = props.a.c.x.y.z; + $[1] = props.a.x.y; $[2] = props.b; $[3] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md index da6912d35e..a2bd99e9a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md @@ -82,10 +82,10 @@ function Component(t0) { } const b = t3; let t4; - if ($[4] !== b || $[5] !== a) { + if ($[4] !== a || $[5] !== b) { t4 = { b, a }; - $[4] = b; - $[5] = a; + $[4] = a; + $[5] = b; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md index bba0b3f139..0089d20af2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md @@ -59,7 +59,7 @@ function Component(t0) { const serverTime = useServerTime(); let t1; let timestampLabel; - if ($[0] !== highlightedItem || $[1] !== serverTime || $[2] !== label) { + if ($[0] !== highlightedItem || $[1] !== label || $[2] !== serverTime) { const highlight = new Highlight(highlightedItem); const time = serverTime.get(); @@ -68,8 +68,8 @@ function Component(t0) { t1 = highlight.render(); $[0] = highlightedItem; - $[1] = serverTime; - $[2] = label; + $[1] = label; + $[2] = serverTime; $[3] = t1; $[4] = timestampLabel; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md index 9c21dc8400..ecdfa88b98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md @@ -66,10 +66,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md index 1b6e91a6fd..f9d725e0b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md @@ -65,10 +65,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md index 2dabc256f9..1ad6f6a047 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md @@ -71,10 +71,10 @@ function Foo() { const shouldCaptureObj = obj != null && CONST_TRUE; const t0 = shouldCaptureObj ? identity(obj) : null; let t1; - if ($[0] !== t0 || $[1] !== obj) { + if ($[0] !== obj || $[1] !== t0) { t1 = [t0, obj]; - $[0] = t0; - $[1] = obj; + $[0] = obj; + $[1] = t0; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md index 4921fd340b..d35cbe266f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md @@ -77,15 +77,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md index 80d046ec18..eda7a0cbfe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md @@ -74,15 +74,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md index 5eff1aa0fe..b5a7827728 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md @@ -21,13 +21,13 @@ function Component(listItem, thread) { let t0; let t1; let t2; - if ($[0] !== thread.threadType || $[1] !== listItem) { + if ($[0] !== listItem || $[1] !== thread.threadType) { const isFoo = isFooThread(thread.threadType); t1 = useBar; t2 = listItem; t0 = getBadgeText(listItem, isFoo); - $[0] = thread.threadType; - $[1] = listItem; + $[0] = listItem; + $[1] = thread.threadType; $[2] = t0; $[3] = t1; $[4] = t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md index 1d4a4b5d67..32cbbb2b91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md @@ -28,7 +28,7 @@ function V0(t0) { const { v1, v2 } = t0; const v5 = v1.v6?.v7; let t1; - if ($[0] !== v5 || $[1] !== v1 || $[2] !== v2) { + if ($[0] !== v1 || $[1] !== v2 || $[2] !== v5) { t1 = ( {v5 != null ? ( @@ -40,9 +40,9 @@ function V0(t0) { )} ); - $[0] = v5; - $[1] = v1; - $[2] = v2; + $[0] = v1; + $[1] = v2; + $[2] = v5; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md index cec64e8cf0..ab409b366d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md @@ -36,7 +36,7 @@ function useTest(t0) { const $ = _c(3); const { isNull, data } = t0; let t1; - if ($[0] !== isNull || $[1] !== data) { + if ($[0] !== data || $[1] !== isNull) { t1 = Builder.makeBuilder(isNull, "hello world") ?.push("1", 2) ?.push(3, { a: 4, b: 5, c: data }) @@ -47,8 +47,8 @@ function useTest(t0) { ) ?.push(7, "8") ?.push("8", Builder.makeBuilder(!isNull)?.push(9).vals)?.vals; - $[0] = isNull; - $[1] = data; + $[0] = data; + $[1] = isNull; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md index 31180b2236..fe7857a355 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md @@ -38,7 +38,7 @@ import { Stringify } from "shared-runtime"; function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.value.length || $[1] !== props.cond) { + if ($[0] !== props.cond || $[1] !== props.value.length) { const label = fbt._( { "*": "{number} bars", _1: "1 bar" }, [fbt._plural(props.value.length, "number")], @@ -51,8 +51,8 @@ function Component(props) { label={label.toString()} /> ) : null; - $[0] = props.value.length; - $[1] = props.cond; + $[0] = props.cond; + $[1] = props.value.length; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md index 73674e5fff..496d80d52b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md @@ -77,10 +77,10 @@ function Component(t0) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== result) { + if ($[4] !== result || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = result; + $[4] = result; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md index 2e9daceed7..1b49552bea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md @@ -26,8 +26,8 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); const item = props.item; - let t0; let baseVideos; + let t0; let thumbnails; if ($[0] !== item) { thumbnails = []; @@ -40,12 +40,12 @@ function Component(props) { } }); $[0] = item; - $[1] = t0; - $[2] = baseVideos; + $[1] = baseVideos; + $[2] = t0; $[3] = thumbnails; } else { - t0 = $[1]; - baseVideos = $[2]; + baseVideos = $[1]; + t0 = $[2]; thumbnails = $[3]; } t0 = undefined; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md index 9aa75b2162..aece9665c9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const [bar] = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md index ded1eb4006..3681e9c469 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const bar = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md index f0a46be168..3e66b20041 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const { bar } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md index 7c9c6a5028..1201a9a737 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md @@ -73,14 +73,14 @@ function foo(props) { } const header = t0; let y; - if ($[5] !== x || $[6] !== props.b || $[7] !== props.c) { + if ($[5] !== props.b || $[6] !== props.c || $[7] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[5] = x; - $[6] = props.b; - $[7] = props.c; + $[5] = props.b; + $[6] = props.c; + $[7] = x; $[8] = y; $[9] = x; } else { @@ -103,15 +103,15 @@ function foo(props) { } const content = t1; let t2; - if ($[13] !== header || $[14] !== content) { + if ($[13] !== content || $[14] !== header) { t2 = ( <> {header} {content} ); - $[13] = header; - $[14] = content; + $[13] = content; + $[14] = header; $[15] = t2; } else { t2 = $[15]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md index 36b1d4541a..143496678e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md @@ -53,30 +53,30 @@ import { c as _c } from "react/compiler-runtime"; // note: comments are for the // emitted function foo(props) { const $ = _c(14); - let x; let t0; + let x; if ($[0] !== props.a) { x = []; x.push(props.a); t0 =
{x}
; $[0] = props.a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const header = t0; let y; - if ($[3] !== x || $[4] !== props.b || $[5] !== props.c) { + if ($[3] !== props.b || $[4] !== props.c || $[5] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[3] = x; - $[4] = props.b; - $[5] = props.c; + $[3] = props.b; + $[4] = props.c; + $[5] = x; $[6] = y; $[7] = x; } else { @@ -99,15 +99,15 @@ function foo(props) { } const content = t1; let t2; - if ($[11] !== header || $[12] !== content) { + if ($[11] !== content || $[12] !== header) { t2 = ( <> {header} {content} ); - $[11] = header; - $[12] = content; + $[11] = content; + $[12] = header; $[13] = t2; } else { t2 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md index 5f10f44202..36a68d07c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md @@ -43,9 +43,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); - let text; let t0; let t1; + let text; if ($[0] !== statusName) { const { status, text: t2 } = foo(statusName); text = t2; @@ -54,13 +54,13 @@ function Component(statusName) { t1 = identity(bg); t0 = identity(color); $[0] = statusName; - $[1] = text; - $[2] = t0; - $[3] = t1; + $[1] = t0; + $[2] = t1; + $[3] = text; } else { - text = $[1]; - t0 = $[2]; - t1 = $[3]; + t0 = $[1]; + t1 = $[2]; + text = $[3]; } let t2; if ($[4] !== text) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md index e2cd53bd0d..d8e991dc46 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md @@ -46,9 +46,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); + let font; let t0; let text; - let font; if ($[0] !== statusName) { const { status, text: t1 } = foo(statusName); text = t1; @@ -58,13 +58,13 @@ function Component(statusName) { t0 = identity(color); $[0] = statusName; - $[1] = t0; - $[2] = text; - $[3] = font; + $[1] = font; + $[2] = t0; + $[3] = text; } else { - t0 = $[1]; - text = $[2]; - font = $[3]; + font = $[1]; + t0 = $[2]; + text = $[3]; } const bg = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..788109636b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -34,8 +34,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); - let y; let t0; + let y; if ($[0] !== props) { const x = []; bb0: switch (props.p0) { @@ -63,19 +63,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[4] !== child || $[5] !== y) { t1 = {child}; - $[4] = y; - $[5] = child; + $[4] = child; + $[5] = y; $[6] = t1; } else { t1 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..2628982655 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -29,8 +29,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(6); - let y; let t0; + let y; if ($[0] !== props) { const x = []; switch (props.p0) { @@ -45,19 +45,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[3] !== child || $[4] !== y) { t1 = {child}; - $[3] = y; - $[4] = child; + $[3] = child; + $[4] = y; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md index f106382d64..4864c51a1f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -64,8 +64,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -85,15 +85,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md index 77fd38aea1..c661094fdd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md @@ -55,7 +55,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -70,8 +70,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,15 +91,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md index d064a48b71..7ac6486b47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md @@ -18,10 +18,10 @@ function App() { const $ = _c(3); const [foo, bar] = useContext(MyContext); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md index f82af06866..3eac66304b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md @@ -22,10 +22,10 @@ function App() { const { foo } = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md index 573b6db231..4cca8b19d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md @@ -22,10 +22,10 @@ function App() { const [foo] = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md index 03ce7f97ba..f5a3916626 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md @@ -22,10 +22,10 @@ function App() { const { joe: t0, bar } = useContext(MyContext); const { foo } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md index 55387503cf..0888d67b2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md @@ -22,10 +22,10 @@ function App() { const foo = context.foo; const bar = context.bar; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md index 268fa8d7eb..2c27360c9f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md @@ -42,9 +42,9 @@ import { mutate, setProperty, throwErrorWithMessageIf } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { value, cond } = t0; - let y; let t1; - if ($[0] !== value || $[1] !== cond) { + let y; + if ($[0] !== cond || $[1] !== value) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { y = [value]; @@ -68,13 +68,13 @@ function useFoo(t0) { } y.push(x); } - $[0] = value; - $[1] = cond; - $[2] = y; - $[3] = t1; + $[0] = cond; + $[1] = value; + $[2] = t1; + $[3] = y; } else { - y = $[2]; - t1 = $[3]; + t1 = $[2]; + y = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md index 700df01c5c..5e57a46018 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md @@ -33,7 +33,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let x; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { try { const y = []; y.push(props.y); @@ -43,8 +43,8 @@ function Component(props) { e.push(props.e); x = e; } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md index c740c7d6b6..921d657e16 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md @@ -32,7 +32,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { try { @@ -46,8 +46,8 @@ function Component(props) { break bb0; } } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md index b04bd53458..562c0bc1c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md @@ -32,8 +32,8 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -47,11 +47,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md index af5f2ebfcf..71a59aba2f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md @@ -33,8 +33,8 @@ const { shallowCopy, throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -52,11 +52,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md index 54d5be2d6b..c3c45beb86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md @@ -78,10 +78,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -95,10 +95,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md index 072c6d03d9..4acbd2dfdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md @@ -76,10 +76,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -93,10 +93,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md index caa74267f3..5016b0c4df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md index a92abd4ca5..3f341fc665 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md index fbd13dc1b0..7fa86838e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md @@ -38,22 +38,22 @@ function component(a) { const f = typeof t.t; let t0; if ( - $[0] !== z || - $[1] !== p || - $[2] !== q || + $[0] !== e || + $[1] !== f || + $[2] !== m || $[3] !== n || - $[4] !== m || - $[5] !== e || - $[6] !== f + $[4] !== p || + $[5] !== q || + $[6] !== z ) { t0 = { z, p, q, n, m, e, f }; - $[0] = z; - $[1] = p; - $[2] = q; + $[0] = e; + $[1] = f; + $[2] = m; $[3] = n; - $[4] = m; - $[5] = e; - $[6] = f; + $[4] = p; + $[5] = q; + $[6] = z; $[7] = t0; } else { t0 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md index 663a6788f3..7f79cae4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md @@ -89,10 +89,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..d94a5e7e37 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -109,10 +109,10 @@ function Inner(props) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== output) { + if ($[4] !== output || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = output; + $[4] = output; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md index 99effce934..cf0093ea94 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md @@ -91,10 +91,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md index 1292ea1489..c3e115fa0d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md @@ -51,7 +51,7 @@ function Component(props) { } const exit = t0; let t1; - if ($[2] !== item.value || $[3] !== exit) { + if ($[2] !== exit || $[3] !== item.value) { t1 = () => { const cleanup = GlobalEventEmitter.addListener("onInput", () => { if (item.value) { @@ -60,8 +60,8 @@ function Component(props) { }); return () => cleanup.remove(); }; - $[2] = item.value; - $[3] = exit; + $[2] = exit; + $[3] = item.value; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md index b6a4187355..28d3e33359 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md @@ -62,13 +62,13 @@ function Component(props) { {w} ); - let condition = $[2] !== x || $[3] !== w; + let condition = $[2] !== w || $[3] !== x; if (!condition) { let old$t1 = $[4]; $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(7:10)"); } - $[2] = x; - $[3] = w; + $[2] = w; + $[3] = x; $[4] = t1; if (condition) { t1 = ( From d923cf4684efdb1187b78049b0b09b69eeab1898 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 15:16:19 -0500 Subject: [PATCH 084/422] [compiler] Add fixture for objectexpr computed key bug We were bailing out on complex computed-key syntax (prior to #31344) as we assumed that this caused bugs (due to inferring computed key rvalues to have `freeze` effects). This fixture shows that this bailout is unrelated to the underlying bug --- ...nstruction-hoisted-sequence-expr.expect.md | 109 ++++++++++++++++++ ...fter-construction-hoisted-sequence-expr.js | 31 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 141 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md new file mode 100644 index 0000000000..dcca6cddd8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md @@ -0,0 +1,109 @@ + +## Input + +```javascript +import {identity, mutate} from 'shared-runtime'; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const key = {}; + const tmp = (mutate(key), key); + const context = { + // Here, `tmp` is frozen (as it's inferred to be a primitive/string) + [tmp]: identity([props.value]), + }; + mutate(key); + return [context, key]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 42}], + sequentialRenders: [{value: 42}, {value: 42}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { identity, mutate } from "shared-runtime"; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const $ = _c(8); + let t0; + let key; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + key = {}; + t0 = (mutate(key), key); + $[0] = t0; + $[1] = key; + } else { + t0 = $[0]; + key = $[1]; + } + const tmp = t0; + let t1; + if ($[2] !== props.value) { + t1 = identity([props.value]); + $[2] = props.value; + $[3] = t1; + } else { + t1 = $[3]; + } + let t2; + if ($[4] !== t1) { + t2 = { [tmp]: t1 }; + $[4] = t1; + $[5] = t2; + } else { + t2 = $[5]; + } + const context = t2; + + mutate(key); + let t3; + if ($[6] !== context) { + t3 = [context, key]; + $[6] = context; + $[7] = t3; + } else { + t3 = $[7]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], + sequentialRenders: [{ value: 42 }, { value: 42 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js new file mode 100644 index 0000000000..94befbdd17 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js @@ -0,0 +1,31 @@ +import {identity, mutate} from 'shared-runtime'; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const key = {}; + const tmp = (mutate(key), key); + const context = { + // Here, `tmp` is frozen (as it's inferred to be a primitive/string) + [tmp]: identity([props.value]), + }; + mutate(key); + return [context, key]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 42}], + sequentialRenders: [{value: 42}, {value: 42}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..76914f1dd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -478,6 +478,7 @@ const skipFilter = new Set([ // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', + 'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr', 'bug-invalid-hoisting-functionexpr', 'bug-try-catch-maybe-null-dependency', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', From b8097aad6bfce4ebafff83fc30bedfd536e6ae7f Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:06:55 -0500 Subject: [PATCH 085/422] [compiler][be] Stabilize compiler output: sort deps and decls by name All dependencies and declarations of a reactive scope can be reordered to scope start/end. i.e. generated code does not depend on conditional short-circuiting logic as dependencies are inferred to have no side effects. Sorting these by name helps us get higher signal compilation snapshot diffs when upgrading the compiler and testing PRs internally at Meta --- .../ReactiveScopes/CodegenReactiveFunction.ts | 51 ++++++++++++++++++- ...ng-primitive-as-dep-nested-scope.expect.md | 6 +-- .../compiler/array-at-effect.expect.md | 6 +-- .../array-expression-spread.expect.md | 6 +-- .../compiler/array-property-call.expect.md | 10 ++-- .../bug-invalid-phi-as-dependency.expect.md | 6 +-- ...fun-alias-captured-mutate-2-iife.expect.md | 6 +-- ...ring-fun-alias-captured-mutate-2.expect.md | 6 +-- ...alias-captured-mutate-arr-2-iife.expect.md | 6 +-- ...-fun-alias-captured-mutate-arr-2.expect.md | 6 +-- ...c-alias-captured-mutate-arr-iife.expect.md | 6 +-- ...g-func-alias-captured-mutate-arr.expect.md | 6 +-- ...-func-alias-captured-mutate-iife.expect.md | 6 +-- ...uring-func-alias-captured-mutate.expect.md | 6 +-- ...turing-function-member-expr-call.expect.md | 6 +-- .../fixtures/compiler/component.expect.md | 12 ++--- .../compiler/computed-call-spread.expect.md | 8 +-- .../compiler/dependencies-outputs.expect.md | 6 +-- .../destructure-in-branch-ssa.expect.md | 8 +-- ...g-same-property-identifier-names.expect.md | 6 +-- .../fixtures/compiler/destructuring.expect.md | 34 ++++++------- ...er-declaration-of-previous-scope.expect.md | 10 ++-- .../existing-variables-with-c-name.expect.md | 6 +-- .../compiler/fast-refresh-reloading.expect.md | 6 +-- ...-mutable-range-destructured-prop.expect.md | 6 +-- ...btparam-with-jsx-element-content.expect.md | 6 +-- ...oop-with-value-block-initializer.expect.md | 6 +-- ...onmutating-loop-local-collection.expect.md | 6 +-- ...pression-prototype-call-mutating.expect.md | 6 +-- ...unctionexpr-conditional-access-2.expect.md | 6 +-- .../functionexpr–conditional-access.expect.md | 6 +-- ...bals-dont-resolve-local-useState.expect.md | 6 +-- .../fixtures/compiler/hook-noAlias.expect.md | 6 +-- .../compiler/hooks-with-prefix.expect.md | 6 +-- ...incompatible-destructuring-kinds.expect.md | 14 ++--- ...-promoted-to-outer-scope-dynamic.expect.md | 20 ++++---- ...jsx-outlining-child-stored-in-id.expect.md | 12 ++--- .../jsx-outlining-jsx-stored-in-id.expect.md | 18 +++---- .../jsx-outlining-separate-nested.expect.md | 22 ++++---- .../compiler/jsx-outlining-simple.expect.md | 18 +++---- ...-tag-evaluation-order-non-global.expect.md | 16 +++--- .../lambda-capture-returned-alias.expect.md | 6 +-- .../lower-context-acess-multiple.expect.md | 6 +-- .../lower-context-selector-simple.expect.md | 6 +-- ...ge-consecutive-scopes-reordering.expect.md | 6 +-- .../compiler/method-call-computed.expect.md | 8 +-- .../compiler/method-call-fn-call.expect.md | 8 +-- .../fixtures/compiler/method-call.expect.md | 6 +-- ...pture-in-unsplittable-memo-block.expect.md | 10 ++-- .../object-shorthand-method-nested.expect.md | 6 +-- ...al-member-expression-as-memo-dep.expect.md | 6 +-- ...ession-single-with-unconditional.expect.md | 6 +-- ...ptional-member-expression-single.expect.md | 6 +-- ...ession-with-conditional-optional.expect.md | 12 ++--- ...mber-expression-with-conditional.expect.md | 12 ++--- ...rly-return-within-reactive-scope.expect.md | 10 ++-- ...Callback-in-other-reactive-block.expect.md | 8 +-- ...k-reordering-deplist-controlflow.expect.md | 16 +++--- ...useMemo-conditional-access-alloc.expect.md | 6 +-- ...eMemo-conditional-access-noAlloc.expect.md | 6 +-- .../useMemo-in-other-reactive-block.expect.md | 8 +-- ...-reordering-depslist-controlflow.expect.md | 6 +-- .../primitive-as-dep-nested-scope.expect.md | 6 +-- ...signed-loop-force-scopes-enabled.expect.md | 8 +-- ...rly-return-within-reactive-scope.expect.md | 8 +-- ...rly-return-within-reactive-scope.expect.md | 8 +-- .../iife-return-modified-later-phi.expect.md | 6 +-- ...al-member-expression-as-memo-dep.expect.md | 6 +-- ...ession-single-with-unconditional.expect.md | 6 +-- ...ptional-member-expression-single.expect.md | 6 +-- ...rly-return-within-reactive-scope.expect.md | 18 +++---- ...hi-type-inference-property-store.expect.md | 6 +-- ...r-function-cond-access-local-var.expect.md | 6 +-- ...function-cond-access-not-hoisted.expect.md | 6 +-- ...n-uncond-access-hoists-other-dep.expect.md | 6 +-- ...uncond-optional-hoists-other-dep.expect.md | 12 ++--- .../memberexpr-join-optional-chain2.expect.md | 6 +-- .../promote-uncond.expect.md | 8 +-- ...a-renaming-unconditional-ternary.expect.md | 8 +-- .../switch-non-final-default.expect.md | 16 +++--- .../switch.expect.md | 16 +++--- ...value-modified-in-catch-escaping.expect.md | 6 +-- ...atch-try-value-modified-in-catch.expect.md | 6 +-- .../useMemo-multiple-if-else.expect.md | 16 +++--- ...-analysis-interleaved-reactivity.expect.md | 6 +-- ...ve-via-mutation-of-computed-load.expect.md | 12 ++--- ...ve-via-mutation-of-property-load.expect.md | 6 +-- .../reassignment-separate-scopes.expect.md | 16 +++--- ...eactive-cond-deps-break-in-scope.expect.md | 6 +-- ...active-cond-deps-return-in-scope.expect.md | 16 +++--- ...function-cond-access-not-hoisted.expect.md | 6 +-- .../hoist-deps-diff-ssa-instance.expect.md | 6 +-- .../jump-poisoned/break-in-scope.expect.md | 6 +-- .../loop-break-in-scope.expect.md | 6 +-- ...e-if-nonexhaustive-poisoned-deps.expect.md | 10 ++-- ...-if-nonexhaustive-poisoned-deps1.expect.md | 10 ++-- .../jump-poisoned/return-in-scope.expect.md | 16 +++--- .../return-poisons-outer-scope.expect.md | 10 ++-- ...p-target-within-scope-loop-break.expect.md | 6 +-- ...e-if-exhaustive-nonpoisoned-deps.expect.md | 10 ++-- ...-if-exhaustive-nonpoisoned-deps1.expect.md | 10 ++-- .../memberexpr-join-optional-chain2.expect.md | 6 +-- .../promote-uncond.expect.md | 6 +-- ...duce-if-exhaustive-poisoned-deps.expect.md | 18 +++---- .../subpath-order1.expect.md | 6 +-- .../superpath-order1.expect.md | 6 +-- .../uncond-access-in-mutable-range.expect.md | 6 +-- .../uncond-nonoverlap-descendant.expect.md | 6 +-- .../reordering-across-blocks.expect.md | 6 +-- ...ed-property-load-for-method-call.expect.md | 6 +-- ...uned-scope-leaks-value-via-alias.expect.md | 6 +-- ...invalid-pruned-scope-leaks-value.expect.md | 6 +-- ...o-invalid-reactivity-value-block.expect.md | 6 +-- ...lack-of-phi-types-explicit-types.expect.md | 6 +-- ...ng-memoization-lack-of-phi-types.expect.md | 6 +-- .../repro-no-value-for-temporary.expect.md | 6 +-- ...ro-propagate-type-of-ternary-jsx.expect.md | 8 +-- ...epro-slow-validate-preserve-memo.expect.md | 6 +-- ...erge-overlapping-reactive-scopes.expect.md | 6 +-- ...ble-code-early-return-in-useMemo.expect.md | 6 +-- .../fixtures/compiler/repro.expect.md | 10 ++-- .../rest-param-with-array-pattern.expect.md | 6 +-- .../rest-param-with-identifier.expect.md | 6 +-- ...param-with-object-spread-pattern.expect.md | 6 +-- ...s-dep-and-redeclare-maybe-frozen.expect.md | 14 ++--- ...me-variable-as-dep-and-redeclare.expect.md | 24 ++++----- ...assignment-to-scope-declarations.expect.md | 14 ++--- ...ixed-local-and-scope-declaration.expect.md | 14 ++--- .../switch-non-final-default.expect.md | 16 +++--- .../fixtures/compiler/switch.expect.md | 16 +++--- .../todo.jsx-outlining-children.expect.md | 12 ++--- ...odo.jsx-outlining-duplicate-prop.expect.md | 12 ++--- ...ntext-access-array-destructuring.expect.md | 6 +-- ...text-access-destructure-multiple.expect.md | 6 +-- ...r-context-access-mixed-array-obj.expect.md | 6 +-- ...text-access-nested-destructuring.expect.md | 6 +-- ...wer-context-access-property-load.expect.md | 6 +-- .../try-catch-in-nested-scope.expect.md | 16 +++--- ...value-modified-in-catch-escaping.expect.md | 6 +-- ...atch-try-value-modified-in-catch.expect.md | 6 +-- .../try-catch-with-catch-param.expect.md | 10 ++-- .../compiler/try-catch-with-return.expect.md | 10 ++-- ...type-provider-log-default-import.expect.md | 12 ++--- .../compiler/type-provider-log.expect.md | 12 ++--- ...r-store-capture-namespace-import.expect.md | 20 ++++---- .../type-provider-store-capture.expect.md | 20 ++++---- .../fixtures/compiler/unary-expr.expect.md | 24 ++++----- .../use-operator-call-expression.expect.md | 6 +-- .../use-operator-conditional.expect.md | 6 +-- .../use-operator-method-call.expect.md | 6 +-- .../useEffect-nested-lambdas.expect.md | 6 +-- .../useState-unpruned-dependency.expect.md | 6 +-- 152 files changed, 724 insertions(+), 677 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 297c771254..167db6dede 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -34,6 +34,7 @@ import { ReactiveInstruction, ReactiveScope, ReactiveScopeBlock, + ReactiveScopeDeclaration, ReactiveScopeDependency, ReactiveTerminal, ReactiveValue, @@ -572,7 +573,8 @@ function codegenReactiveScope( const changeExpressions: Array = []; const changeExpressionComments: Array = []; const outputComments: Array = []; - for (const dep of scope.dependencies) { + + for (const dep of [...scope.dependencies].sort(compareScopeDependency)) { const index = cx.nextCacheIndex; changeExpressionComments.push(printDependencyComment(dep)); const comparison = t.binaryExpression( @@ -615,7 +617,10 @@ function codegenReactiveScope( ); } let firstOutputIndex: number | null = null; - for (const [, {identifier}] of scope.declarations) { + + for (const [, {identifier}] of [...scope.declarations].sort(([, a], [, b]) => + compareScopeDeclaration(a, b), + )) { const index = cx.nextCacheIndex; if (firstOutputIndex === null) { firstOutputIndex = index; @@ -2566,3 +2571,45 @@ function convertIdentifier(identifier: Identifier): t.Identifier { ); return t.identifier(identifier.name.value); } + +function compareScopeDependency( + a: ReactiveScopeDependency, + b: ReactiveScopeDependency, +): number { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected named identifier for dependency', + loc: a.identifier.loc, + }, + ); + const aName = [ + a.identifier.name.value, + ...a.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`), + ].join('.'); + const bName = [ + b.identifier.name.value, + ...b.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`), + ].join('.'); + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; +} + +function compareScopeDeclaration( + a: ReactiveScopeDeclaration, + b: ReactiveScopeDeclaration, +): number { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected named identifier for declaration', + loc: a.identifier.loc, + }, + ); + const aName = a.identifier.name.value; + const bName = b.identifier.name.value; + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md index cb550b4230..6702b26e92 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md @@ -47,7 +47,7 @@ import { identity, mutate, setProperty } from "shared-runtime"; function AllocatingPrimitiveAsDepNested(props) { const $ = _c(5); let t0; - if ($[0] !== props.b || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.b) { const x = {}; mutate(x); const t1 = identity(props.b) + 1; @@ -62,8 +62,8 @@ function AllocatingPrimitiveAsDepNested(props) { const y = t2; setProperty(x, props.a); t0 = [x, y]; - $[0] = props.b; - $[1] = props.a; + $[0] = props.a; + $[1] = props.b; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md index 3aa51ba6d6..a8bad51215 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md @@ -41,7 +41,7 @@ function ArrayAtTest(props) { } const arr = t1; let t2; - if ($[4] !== props.y || $[5] !== arr) { + if ($[4] !== arr || $[5] !== props.y) { let t3; if ($[7] !== props.y) { t3 = bar(props.y); @@ -51,8 +51,8 @@ function ArrayAtTest(props) { t3 = $[8]; } t2 = arr.at(t3); - $[4] = props.y; - $[5] = arr; + $[4] = arr; + $[5] = props.y; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md index 46ae949238..f3af7efcf6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md @@ -22,10 +22,10 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.foo || $[1] !== props.bar) { + if ($[0] !== props.bar || $[1] !== props.foo) { t0 = [0, ...props.foo, null, ...props.bar, "z"]; - $[0] = props.foo; - $[1] = props.bar; + $[0] = props.bar; + $[1] = props.foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md index 7aa47c5803..6618be4a6c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md @@ -24,18 +24,18 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(11); - let t0; let a; + let t0; if ($[0] !== props.a || $[1] !== props.b) { a = [props.a, props.b, "hello"]; t0 = a.push(42); $[0] = props.a; $[1] = props.b; - $[2] = t0; - $[3] = a; + $[2] = a; + $[3] = t0; } else { - t0 = $[2]; - a = $[3]; + a = $[2]; + t0 = $[3]; } const x = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md index 32e2c9fd64..09d2d8800b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md @@ -70,10 +70,10 @@ function Component() { throw new Error("invariant broken"); } let t1; - if ($[1] !== obj || $[2] !== boxedInner) { + if ($[1] !== boxedInner || $[2] !== obj) { t1 = ; - $[1] = obj; - $[2] = boxedInner; + $[1] = boxedInner; + $[2] = obj; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md index 65ab9c277c..5e0b32709b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md @@ -32,7 +32,7 @@ import { mutate } from "shared-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md index 170f68bade..f9ce3f2e98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md index e315cd401d..81737a1ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md index 7371f3d27a..38590d1559 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md index cc41adcbbc..4882aa822f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md index 34f6a55740..7c94c33e49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md index b2d7048756..60493dd25a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..14532562fb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md index 51679299fe..cab9c9a500 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md @@ -46,10 +46,10 @@ function component(t0) { } const hide = t2; let t3; - if ($[4] !== poke || $[5] !== hide) { + if ($[4] !== hide || $[5] !== poke) { t3 = ; - $[4] = poke; - $[5] = hide; + $[4] = hide; + $[5] = poke; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md index 80d6e6df8c..c6037fd2bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md @@ -46,7 +46,7 @@ function Component(props) { const items = props.items; const maxItems = props.maxItems; let renderedItems; - if ($[0] !== maxItems || $[1] !== items) { + if ($[0] !== items || $[1] !== maxItems) { renderedItems = []; const seen = new Set(); const max = Math.max(0, maxItems); @@ -62,8 +62,8 @@ function Component(props) { break; } } - $[0] = maxItems; - $[1] = items; + $[0] = items; + $[1] = maxItems; $[2] = renderedItems; } else { renderedItems = $[2]; @@ -79,15 +79,15 @@ function Component(props) { t0 = $[4]; } let t1; - if ($[5] !== t0 || $[6] !== renderedItems) { + if ($[5] !== renderedItems || $[6] !== t0) { t1 = (
{t0} {renderedItems}
); - $[5] = t0; - $[6] = renderedItems; + $[5] = renderedItems; + $[6] = t0; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md index cb20d97cb7..0329450b13 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md @@ -16,11 +16,11 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.method || $[1] !== props.a || $[2] !== props.b) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.method) { t0 = foo[props.method](...props.a, null, ...props.b); - $[0] = props.method; - $[1] = props.a; - $[2] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.method; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md index 6fc686cb19..f0f9911c07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md @@ -41,7 +41,7 @@ function foo(a, b) { x = $[1]; } let y; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { y = []; if (x.length) { y.push(x); @@ -49,8 +49,8 @@ function foo(a, b) { if (b) { y.push(b); } - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = y; } else { y = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md index d65082cbc8..b159789106 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md @@ -61,11 +61,11 @@ function useFoo(props) { z = $[4]; } let t0; - if ($[5] !== x || $[6] !== y || $[7] !== myList) { + if ($[5] !== myList || $[6] !== x || $[7] !== y) { t0 = { x, y, myList }; - $[5] = x; - $[6] = y; - $[7] = myList; + $[5] = myList; + $[6] = x; + $[7] = y; $[8] = t0; } else { t0 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md index b86498b922..3e1c4771ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md @@ -41,10 +41,10 @@ function Component(props) { } const sameName = t1; let t2; - if ($[2] !== sameName || $[3] !== renamed) { + if ($[2] !== renamed || $[3] !== sameName) { t2 = [sameName, renamed]; - $[2] = sameName; - $[3] = renamed; + $[2] = renamed; + $[3] = sameName; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md index c175cc558c..f292e83e16 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md @@ -36,45 +36,45 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(18); - let t0; let d; let h; + let t0; if ($[0] !== a) { [d, t0, ...h] = a; $[0] = a; - $[1] = t0; - $[2] = d; - $[3] = h; + $[1] = d; + $[2] = h; + $[3] = t0; } else { - t0 = $[1]; - d = $[2]; - h = $[3]; + d = $[1]; + h = $[2]; + t0 = $[3]; } const [t1] = t0; - let t2; let g; + let t2; if ($[4] !== t1) { ({ e: t2, ...g } = t1); $[4] = t1; - $[5] = t2; - $[6] = g; + $[5] = g; + $[6] = t2; } else { - t2 = $[5]; - g = $[6]; + g = $[5]; + t2 = $[6]; } const { f } = t2; const { l: t3, p } = b; const { m: t4 } = t3; - let t5; let o; + let t5; if ($[7] !== t4) { [t5, ...o] = t4; $[7] = t4; - $[8] = t5; - $[9] = o; + $[8] = o; + $[9] = t5; } else { - t5 = $[8]; - o = $[9]; + o = $[8]; + t5 = $[9]; } const [n] = t5; let t6; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md index 29780eb76c..ce5bfda644 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md @@ -53,8 +53,8 @@ import { ValidateMemoization } from "shared-runtime"; function Component(t0) { const $ = _c(25); const { a, b, c } = t0; - let y; let x; + let y; if ($[0] !== a || $[1] !== b || $[2] !== c) { x = []; if (a) { @@ -73,11 +73,11 @@ function Component(t0) { $[0] = a; $[1] = b; $[2] = c; - $[3] = y; - $[4] = x; + $[3] = x; + $[4] = y; } else { - y = $[3]; - x = $[4]; + x = $[3]; + y = $[4]; } let t1; if ($[7] !== y) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md index 0d671a3de2..5cde3bde23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md @@ -61,10 +61,10 @@ function Component(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== array) { + if ($[4] !== array || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = array; + $[4] = array; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md index ecd03a0b10..4175d23fda 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md @@ -59,10 +59,10 @@ function Component(props) { t3 = $[4]; } let t4; - if ($[5] !== t3 || $[6] !== doubled) { + if ($[5] !== doubled || $[6] !== t3) { t4 = ; - $[5] = t3; - $[6] = doubled; + $[5] = doubled; + $[6] = t3; $[7] = t4; } else { t4 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md index d56f7a98ad..9bb651aa67 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md @@ -61,10 +61,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== el) { + if ($[4] !== el || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = el; + $[4] = el; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md index d58c25b510..56ffb70cb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md @@ -32,7 +32,7 @@ function Component(t0) { const $ = _c(4); const { name, data, icon } = t0; let t1; - if ($[0] !== name || $[1] !== icon || $[2] !== data) { + if ($[0] !== data || $[1] !== icon || $[2] !== name) { t1 = ( {fbt._( @@ -61,9 +61,9 @@ function Component(t0) { )} ); - $[0] = name; + $[0] = data; $[1] = icon; - $[2] = data; + $[2] = name; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md index 24f2e545cc..6ef73de8b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md @@ -67,7 +67,7 @@ const TOTAL = 10; function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.start || $[1] !== props.items) { + if ($[0] !== props.items || $[1] !== props.start) { const items = []; for (let i = props.start ?? 0; i < props.items.length; i++) { const item = props.items[i]; @@ -75,8 +75,8 @@ function Component(props) { } t0 =
{items}
; - $[0] = props.start; - $[1] = props.items; + $[0] = props.items; + $[1] = props.start; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md index cff8c5bd13..4abe630044 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md @@ -91,10 +91,10 @@ function Component(t0) { t5 = $[9]; } let t6; - if ($[10] !== x || $[11] !== b) { + if ($[10] !== b || $[11] !== x) { t6 = [x, b]; - $[10] = x; - $[11] = b; + $[10] = b; + $[11] = x; $[12] = t6; } else { t6 = $[12]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md index be59673c15..9888e96222 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md @@ -59,10 +59,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== a_0) { + if ($[4] !== a_0 || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = a_0; + $[4] = a_0; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..32498e1379 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -36,10 +36,10 @@ function Component(t0) { } const f = t1; let t2; - if ($[2] !== props || $[3] !== f) { + if ($[2] !== f || $[3] !== props) { t2 = props == null ? _temp : f; - $[2] = props; - $[3] = f; + $[2] = f; + $[3] = props; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..4a62bf6f24 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -36,10 +36,10 @@ function Component(props) { } const getLength = t0; let t1; - if ($[2] !== props.bar || $[3] !== getLength) { + if ($[2] !== getLength || $[3] !== props.bar) { t1 = props.bar && getLength(); - $[2] = props.bar; - $[3] = getLength; + $[2] = getLength; + $[3] = props.bar; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md index 7548a3b639..be7f3f1bd2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md @@ -56,10 +56,10 @@ function Component() { t0 = $[1]; } let t1; - if ($[2] !== t0 || $[3] !== state) { + if ($[2] !== state || $[3] !== t0) { t1 =
{state}
; - $[2] = t0; - $[3] = state; + $[2] = state; + $[3] = t0; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md index 96ccd1e2f1..329d57a035 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md @@ -41,10 +41,10 @@ function Component(props) { console.log(props); }, [props.a]); let t1; - if ($[2] !== x || $[3] !== item) { + if ($[2] !== item || $[3] !== x) { t1 = [x, item]; - $[2] = x; - $[3] = item; + $[2] = item; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md index f7e02a53f1..085df625f5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md @@ -73,15 +73,15 @@ function Component() { t1 = $[4]; } let t3; - if ($[5] !== t2 || $[6] !== json) { + if ($[5] !== json || $[6] !== t2) { t3 = (
{t2} {json}
); - $[5] = t2; - $[6] = json; + $[5] = json; + $[6] = t2; $[7] = t3; } else { t3 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md index 970cc50f03..8afc59a80b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md @@ -29,22 +29,22 @@ import { Stringify } from "shared-runtime"; function Component(t0) { const $ = _c(4); - let t1; let a; let b; + let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { a = "a"; const [t2, t3] = [null, null]; t1 = t3; a = t2; - $[0] = t1; - $[1] = a; - $[2] = b; + $[0] = a; + $[1] = b; + $[2] = t1; } else { - t1 = $[0]; - a = $[1]; - b = $[2]; + a = $[0]; + b = $[1]; + t1 = $[2]; } b = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md index becc4066e7..735462657f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md @@ -27,10 +27,10 @@ function Component(props) { const $ = _c(15); const item = useFragment(FRAGMENT, props.item); useFreeze(item); - let t0; let T0; - let t1; let T1; + let t0; + let t1; if ($[0] !== item) { const count = new MaybeMutable(item); @@ -44,15 +44,15 @@ function Component(props) { } t0 = maybeMutate(count); $[0] = item; - $[1] = t0; - $[2] = T0; - $[3] = t1; - $[4] = T1; + $[1] = T0; + $[2] = T1; + $[3] = t0; + $[4] = t1; } else { - t0 = $[1]; - T0 = $[2]; - t1 = $[3]; - T1 = $[4]; + T0 = $[1]; + T1 = $[2]; + t0 = $[3]; + t1 = $[4]; } let t2; if ($[6] !== t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..b84229156b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -83,10 +83,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -98,15 +98,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md index 496282e1ef..7fca963134 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md @@ -52,7 +52,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -66,8 +66,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -94,10 +94,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -109,15 +109,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md index 7f86546cd4..9d2b254c06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md @@ -60,7 +60,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -73,8 +73,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -117,7 +117,7 @@ function _temp(t0) { t3 = $[5]; } let t4; - if ($[6] !== x || $[7] !== t1 || $[8] !== t2 || $[9] !== t3) { + if ($[6] !== t1 || $[7] !== t2 || $[8] !== t3 || $[9] !== x) { t4 = ( {t1} @@ -125,10 +125,10 @@ function _temp(t0) { {t3} ); - $[6] = x; - $[7] = t1; - $[8] = t2; - $[9] = t3; + $[6] = t1; + $[7] = t2; + $[8] = t3; + $[9] = x; $[10] = t4; } else { t4 = $[10]; @@ -140,15 +140,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md index 9e268227a2..09323f5ac6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -63,8 +63,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,10 +91,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -106,15 +106,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md index b1dfbc61c3..d46ce53bb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md @@ -53,23 +53,23 @@ function maybeMutate(x) {} function Component(props) { const $ = _c(11); - let Tag; let T0; + let Tag; let t0; - if ($[0] !== props.component || $[1] !== props.alternateComponent) { + if ($[0] !== props.alternateComponent || $[1] !== props.component) { const maybeMutable = new MaybeMutable(); Tag = props.component; T0 = Tag; t0 = ((Tag = props.alternateComponent), maybeMutate(maybeMutable)); - $[0] = props.component; - $[1] = props.alternateComponent; - $[2] = Tag; - $[3] = T0; + $[0] = props.alternateComponent; + $[1] = props.component; + $[2] = T0; + $[3] = Tag; $[4] = t0; } else { - Tag = $[2]; - T0 = $[3]; + T0 = $[2]; + Tag = $[3]; t0 = $[4]; } let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md index e172ee1039..bac21217c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md @@ -44,7 +44,7 @@ function CaptureNotMutate(props) { } const idx = t0; let aliasedElement; - if ($[2] !== props.el || $[3] !== idx) { + if ($[2] !== idx || $[3] !== props.el) { const element = bar(props.el); const fn = function () { @@ -54,8 +54,8 @@ function CaptureNotMutate(props) { aliasedElement = fn(); mutate(aliasedElement); - $[2] = props.el; - $[3] = idx; + $[2] = idx; + $[3] = props.el; $[4] = aliasedElement; } else { aliasedElement = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md index 47c7a2d743..af9b1df36a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md @@ -21,10 +21,10 @@ function App() { const { foo } = useContext_withSelector(MyContext, _temp); const { bar } = useContext_withSelector(MyContext, _temp2); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md index 0b12c2e250..d13682467b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md @@ -19,10 +19,10 @@ function App() { const $ = _c(3); const { foo, bar } = useContext_withSelector(MyContext, _temp); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md index 5bf1f2cf4d..e5a9081137 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md @@ -60,7 +60,7 @@ function Component() { t2 = $[3]; } let t3; - if ($[4] !== t1 || $[5] !== t0) { + if ($[4] !== t0 || $[5] !== t1) { t3 = (
{t2} @@ -68,8 +68,8 @@ function Component() { {t0}
); - $[4] = t1; - $[5] = t0; + $[4] = t0; + $[5] = t1; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md index 887479c01e..2fc302c8b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md @@ -43,11 +43,11 @@ function foo(a, b, c) { } const y = t1; let t2; - if ($[4] !== x || $[5] !== y.method || $[6] !== b) { + if ($[4] !== b || $[5] !== x || $[6] !== y.method) { t2 = x[y.method](b); - $[4] = x; - $[5] = y.method; - $[6] = b; + $[4] = b; + $[5] = x; + $[6] = y.method; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md index c32777534b..58c06101d3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md @@ -33,11 +33,11 @@ function foo(a, b, c) { const method = x.method; let t1; - if ($[2] !== method || $[3] !== x || $[4] !== b) { + if ($[2] !== b || $[3] !== method || $[4] !== x) { t1 = method.call(x, b); - $[2] = method; - $[3] = x; - $[4] = b; + $[2] = b; + $[3] = method; + $[4] = x; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md index ad45287d1f..fd8a4935a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md @@ -40,10 +40,10 @@ function foo(a, b, c) { } const x = t0; let t1; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { t1 = x.foo(b); - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md index 090e9d889c..a3403e6c8d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md @@ -73,8 +73,8 @@ import { identity, mutate } from "shared-runtime"; function useFoo(t0) { const $ = _c(4); const { a, b } = t0; - let z; let y; + let z; if ($[0] !== a || $[1] !== b) { const x = { a }; y = {}; @@ -83,11 +83,11 @@ function useFoo(t0) { mutate(y); $[0] = a; $[1] = b; - $[2] = z; - $[3] = y; + $[2] = y; + $[3] = z; } else { - z = $[2]; - y = $[3]; + y = $[2]; + z = $[3]; } if (z[0] !== y) { throw new Error("oh no!"); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md index 4b500f52d6..dc1cd699d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md @@ -40,7 +40,7 @@ function useHook(t0) { const { value } = t0; const [state] = useState(false); let t1; - if ($[0] !== value || $[1] !== state) { + if ($[0] !== state || $[1] !== value) { t1 = { getX() { return { @@ -52,8 +52,8 @@ function useHook(t0) { }; }, }; - $[0] = value; - $[1] = state; + $[0] = state; + $[1] = value; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md index 77875f789d..3dd8e73032 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md index 46767056bd..3cd9877813 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md index 6e44a97b45..60a6171ab1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md index 77ded20d93..2674d78c99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md index 10c23085d8..1d4a50d285 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..42b3b21a20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); - let y; let t0; + let y; if ($[0] !== props) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -57,11 +57,11 @@ function Component(props) { } } $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md index 9ca63e23c4..3da133e929 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(7); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; if ($[4] !== minWidth || $[5] !== width) { @@ -55,9 +55,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md index 947e3bd2eb..ee4e4634cb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md @@ -42,9 +42,9 @@ import { Stringify } from "shared-runtime"; function Foo(t0) { const $ = _c(8); const { arr1, arr2, foo } = t0; - let t1; let getVal1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + let t1; + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -55,13 +55,13 @@ function Foo(t0) { t1 = () => [y]; foo ? (y = x.concat(arr2)) : y; $[0] = arr1; - $[1] = foo; - $[2] = arr2; - $[3] = t1; - $[4] = getVal1; + $[1] = arr2; + $[2] = foo; + $[3] = getVal1; + $[4] = t1; } else { - t1 = $[3]; - getVal1 = $[4]; + getVal1 = $[3]; + t1 = $[4]; } const getVal2 = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md index 3a7016f803..f7353ddd5e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md @@ -44,10 +44,10 @@ function Component(t0) { t3 = $[1]; } let t4; - if ($[2] !== t3 || $[3] !== propA) { + if ($[2] !== propA || $[3] !== t3) { t4 = { value: t3, other: propA }; - $[2] = t3; - $[3] = propA; + $[2] = propA; + $[3] = t3; $[4] = t4; } else { t4 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md index f0ce9b9114..c6ad9bcdac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md @@ -34,10 +34,10 @@ function Component(t0) { const t2 = propB?.x.y; let t3; - if ($[0] !== t2 || $[1] !== propA) { + if ($[0] !== propA || $[1] !== t2) { t3 = { value: t2, other: propA }; - $[0] = t2; - $[1] = propA; + $[0] = propA; + $[1] = t2; $[2] = t3; } else { t3 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md index 9d7feacd6d..7a27bb8521 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(6); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; @@ -58,9 +58,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md index e9d2bffb30..5fc0ec510b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md @@ -44,7 +44,7 @@ function Foo(t0) { const { arr1, arr2, foo } = t0; let t1; let val1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -63,8 +63,8 @@ function Foo(t0) { foo ? (y = x.concat(arr2)) : y; t1 = (() => [y])(); $[0] = arr1; - $[1] = foo; - $[2] = arr2; + $[1] = arr2; + $[2] = foo; $[3] = t1; $[4] = val1; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md index e1d4bb5a8a..580a97ac19 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md @@ -49,7 +49,7 @@ import { identity, mutate, setProperty } from "shared-runtime"; function PrimitiveAsDepNested(props) { const $ = _c(5); let t0; - if ($[0] !== props.b || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.b) { const x = {}; mutate(x); const t1 = props.b + 1; @@ -64,8 +64,8 @@ function PrimitiveAsDepNested(props) { const y = t2; setProperty(x, props.a); t0 = [x, y]; - $[0] = props.b; - $[1] = props.a; + $[0] = props.a; + $[1] = props.b; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md index 594e68f24f..de39fc9706 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md @@ -32,15 +32,15 @@ function Component(t0) { const $ = _c(5); const { base, start, increment, test } = t0; let value; - if ($[0] !== base || $[1] !== start || $[2] !== test || $[3] !== increment) { + if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) { value = base; for (let i = start; i < test; i = i + increment, i) { value = value + i; } $[0] = base; - $[1] = start; - $[2] = test; - $[3] = increment; + $[1] = increment; + $[2] = start; + $[3] = test; $[4] = value; } else { value = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md index 476e1c017c..c235681d38 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md @@ -34,7 +34,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(7); let t0; - if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -69,9 +69,9 @@ function Component(props) { break bb0; } } - $[0] = props.cond; - $[1] = props.a; - $[2] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md index bf54b52b59..0e93d32061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md @@ -48,7 +48,7 @@ import { makeArray } from "shared-runtime"; function Component(props) { const $ = _c(6); let t0; - if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -69,9 +69,9 @@ function Component(props) { break bb0; } } - $[0] = props.cond; - $[1] = props.a; - $[2] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md index 744824d0bd..2a3da8b195 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md @@ -29,7 +29,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(3); let items; - if ($[0] !== props.cond || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.cond) { let t0; if (props.cond) { t0 = []; @@ -39,8 +39,8 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props.cond; - $[1] = props.a; + $[0] = props.a; + $[1] = props.cond; $[2] = items; } else { items = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md index d0486cd8c2..28612f2d73 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md index b4a55fcb61..2861ab71c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md index f15b9b8e9b..b5db44aa2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md index 324eb714fc..1972e92237 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md @@ -32,9 +32,9 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(6); - let y; let t0; - if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { + let y; + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,14 +57,14 @@ function Component(props) { } } } - $[0] = props.cond; - $[1] = props.a; - $[2] = props.b; - $[3] = y; - $[4] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md index 1bfaeb1c84..6c7a91ba33 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props.cond || $[2] !== props.a) { + if ($[1] !== props.a || $[2] !== props.cond) { let y; if (props.cond) { y = {}; @@ -53,8 +53,8 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props.cond; - $[2] = props.a; + $[1] = props.a; + $[2] = props.cond; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md index c0f8aa97cd..673dd0d5fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md @@ -58,7 +58,7 @@ function useFoo(t0) { local = $[1]; } let t1; - if ($[2] !== shouldReadA || $[3] !== local) { + if ($[2] !== local || $[3] !== shouldReadA) { t1 = ( { @@ -70,8 +70,8 @@ function useFoo(t0) { shouldInvokeFns={true} /> ); - $[2] = shouldReadA; - $[3] = local; + $[2] = local; + $[3] = shouldReadA; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md index e37b8365a2..abf4c98f23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md @@ -41,7 +41,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a) { + if ($[0] !== a || $[1] !== shouldReadA) { t1 = ( { @@ -53,8 +53,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a; + $[0] = a; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md index 89b4d281f8..d82956e4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md @@ -51,13 +51,13 @@ function Foo(t0) { const fn = t1; useIdentity(null); let x; - if ($[2] !== cond || $[3] !== a.b.c) { + if ($[2] !== a.b.c || $[3] !== cond) { x = makeArray(); if (cond) { x.push(identity(a.b.c)); } - $[2] = cond; - $[3] = a.b.c; + $[2] = a.b.c; + $[3] = cond; $[4] = x; } else { x = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md index 591e04de7b..c81e59ecea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md @@ -50,22 +50,22 @@ function Foo(t0) { const fn = t1; useIdentity(null); let arr; - if ($[2] !== cond || $[3] !== a.b?.c.e) { + if ($[2] !== a.b?.c.e || $[3] !== cond) { arr = makeArray(); if (cond) { arr.push(identity(a.b?.c.e)); } - $[2] = cond; - $[3] = a.b?.c.e; + $[2] = a.b?.c.e; + $[3] = cond; $[4] = arr; } else { arr = $[4]; } let t2; - if ($[5] !== fn || $[6] !== arr) { + if ($[5] !== arr || $[6] !== fn) { t2 = ; - $[5] = fn; - $[6] = arr; + $[5] = arr; + $[6] = fn; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md index df9dec4fb6..277c203525 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(5); let x; - if ($[0] !== props.items?.length || $[1] !== props.items?.edges) { + if ($[0] !== props.items?.edges || $[1] !== props.items?.length) { x = []; x.push(props.items?.length); let t0; @@ -36,8 +36,8 @@ function Component(props) { t0 = $[4]; } x.push(t0); - $[0] = props.items?.length; - $[1] = props.items?.edges; + $[0] = props.items?.edges; + $[1] = props.items?.length; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md index 902a1578c8..30cea1e2c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md @@ -38,15 +38,15 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(4); let x; - if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { + if ($[0] !== other || $[1] !== props.a.a.a || $[2] !== props.a.b) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a.a.a; - $[1] = props.a.b; - $[2] = other; + $[0] = other; + $[1] = props.a.a.a; + $[2] = props.a.b; $[3] = x; } else { x = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md index c5cf366fb6..923830fb4b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md @@ -39,11 +39,11 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { + if ($[2] !== props.bar || $[3] !== props.cond || $[4] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props.cond; - $[3] = props.foo; - $[4] = props.bar; + $[2] = props.bar; + $[3] = props.cond; + $[4] = props.foo; $[5] = x; } else { x = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md index 37846215b1..fee31c9faf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md @@ -35,8 +35,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { @@ -65,19 +65,19 @@ function Component(props) { t0 = ; $[0] = props.p0; $[1] = props.p2; - $[2] = y; - $[3] = t0; + $[2] = t0; + $[3] = y; } else { - y = $[2]; - t0 = $[3]; + t0 = $[2]; + y = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md index 1be4143849..6290668015 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md @@ -30,8 +30,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { @@ -48,19 +48,19 @@ function Component(props) { $[0] = props.p0; $[1] = props.p2; $[2] = props.p3; - $[3] = y; - $[4] = t0; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md index f69994b0a8..5cd81990e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md @@ -34,7 +34,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let x; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { try { const y = []; y.push(props.y); @@ -44,8 +44,8 @@ function Component(props) { e.push(props.e); x = e; } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md index bc47228371..dc41af6275 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md @@ -33,7 +33,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { try { @@ -47,8 +47,8 @@ function Component(props) { break bb0; } } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md index d654221dc8..a75b592b83 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md @@ -39,10 +39,10 @@ function Component(props) { bb0: { let y; if ( - $[0] !== props.cond || - $[1] !== props.a || - $[2] !== props.cond2 || - $[3] !== props.b + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.cond || + $[3] !== props.cond2 ) { y = []; if (props.cond) { @@ -54,10 +54,10 @@ function Component(props) { } y.push(props.b); - $[0] = props.cond; - $[1] = props.a; - $[2] = props.cond2; - $[3] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = props.cond2; $[4] = y; $[5] = t0; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md index 7480362a43..c6331bd4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md @@ -54,10 +54,10 @@ function Component(props) { } const c = t0; let t1; - if ($[3] !== c || $[4] !== a) { + if ($[3] !== a || $[4] !== c) { t1 = [c, a]; - $[3] = c; - $[4] = a; + $[3] = a; + $[4] = c; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md index 29e3e2757f..5ac29886cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md @@ -20,11 +20,11 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(8); let items; - if ($[0] !== props.key || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.key) { items = bar(); mutate(items[props.key], props.a); - $[0] = props.key; - $[1] = props.a; + $[0] = props.a; + $[1] = props.key; $[2] = items; } else { items = $[2]; @@ -41,10 +41,10 @@ function Component(props) { } const count = t1; let t2; - if ($[5] !== items || $[6] !== count) { + if ($[5] !== count || $[6] !== items) { t2 = { items, count }; - $[5] = items; - $[6] = count; + $[5] = count; + $[6] = items; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md index 3408f707d6..bb76df4de0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md @@ -40,10 +40,10 @@ function Component(props) { } const count = t1; let t2; - if ($[4] !== items || $[5] !== count) { + if ($[4] !== count || $[5] !== items) { t2 = { items, count }; - $[4] = items; - $[5] = count; + $[4] = count; + $[5] = items; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md index e682a01086..422f4cf547 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md @@ -42,8 +42,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(10); - let x; let t0; + let x; if ($[0] !== a) { x = []; if (a) { @@ -52,11 +52,11 @@ function foo(a, b, c) { t0 =
{x}
; $[0] = a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const y = t0; bb0: switch (b) { @@ -83,15 +83,15 @@ function foo(a, b, c) { } } let t1; - if ($[7] !== y || $[8] !== x) { + if ($[7] !== x || $[8] !== y) { t1 = (
{y} {x}
); - $[7] = y; - $[8] = x; + $[7] = x; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md index 3ad544344d..f3ddf57ec0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md @@ -34,7 +34,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md index 449aa6d3d8..5700634449 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md @@ -31,9 +31,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..18e9faf63b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== a.b.c || $[1] !== shouldReadA) { t1 = ( { @@ -50,8 +50,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a.b.c; + $[0] = a.b.c; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md index 701702f9dd..2dd9362404 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md @@ -80,10 +80,10 @@ function useFoo(t0) { x = $[6]; } let t1; - if ($[7] !== y || $[8] !== x.a.b) { + if ($[7] !== x.a.b || $[8] !== y) { t1 = [y, x.a.b]; - $[7] = y; - $[8] = x.a.b; + $[7] = x.a.b; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md index b4c25d5f45..e024bc893a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md index 359ba0dcde..055d1ce12e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; for (let i = 0; i < 5; i++) { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md index bb47587687..1c2162352b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md @@ -42,8 +42,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -77,11 +77,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md index 59225b5155..ca8228e2db 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md index 7a7f9a4b6e..ce12fb17b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md @@ -33,9 +33,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md index 2b925c2479..058bf85b69 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md @@ -39,8 +39,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { input, cond } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -61,11 +61,11 @@ function useFoo(t0) { } $[0] = cond; $[1] = input; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md index 291998c8ad..2715a0c92d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md @@ -40,7 +40,7 @@ function useFoo(t0) { const $ = _c(3); const { input, max } = t0; let x; - if ($[0] !== max || $[1] !== input.a.b) { + if ($[0] !== input.a.b || $[1] !== max) { x = []; let i = 0; while (true) { @@ -52,8 +52,8 @@ function useFoo(t0) { x.push(i); x.push(input.a.b); - $[0] = max; - $[1] = input.a.b; + $[0] = input.a.b; + $[1] = max; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md index 84b8fa1d43..845ea4b5ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md @@ -33,8 +33,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, hasAB, returnNull } = t0; - let x; let t1; + let x; if ($[0] !== hasAB || $[1] !== input.a || $[2] !== returnNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -68,11 +68,11 @@ function useFoo(t0) { $[0] = hasAB; $[1] = input.a; $[2] = returnNull; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md index a55922f610..d3e463495b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond2, cond1 } = t0; - let x; let t1; + let x; if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== input.a.b) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond1; $[1] = cond2; $[2] = input.a.b; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md index 8d69c008c5..7818ca4e0d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(5); let x; - if ($[0] !== props.items?.length || $[1] !== props.items?.edges) { + if ($[0] !== props.items?.edges || $[1] !== props.items?.length) { x = []; x.push(props.items?.length); let t0; @@ -35,8 +35,8 @@ function Component(props) { t0 = $[4]; } x.push(t0); - $[0] = props.items?.length; - $[1] = props.items?.edges; + $[0] = props.items?.edges; + $[1] = props.items?.length; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..d3a61a1019 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -36,14 +36,14 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== other || $[1] !== props.a) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; + $[0] = other; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md index 01ab4f6b0a..41a7a25d63 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md @@ -34,9 +34,9 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, inputHasAB, inputHasABC } = t0; - let x; let t1; - if ($[0] !== inputHasABC || $[1] !== input.a || $[2] !== inputHasAB) { + let x; + if ($[0] !== input.a || $[1] !== inputHasAB || $[2] !== inputHasABC) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -75,14 +75,14 @@ function useFoo(t0) { x.push(t2); } } - $[0] = inputHasABC; - $[1] = input.a; - $[2] = inputHasAB; - $[3] = x; - $[4] = t1; + $[0] = input.a; + $[1] = inputHasAB; + $[2] = inputHasABC; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md index a62223d72d..03e3e96397 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md @@ -40,14 +40,14 @@ import { identity } from "shared-runtime"; function useConditionalSubpath1(props, cond) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.b = props.a.b; if (identity(cond)) { x.a = props.a; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md index 02117feee2..5b246175f9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md @@ -48,14 +48,14 @@ function useConditionalSuperpath1(t0) { const $ = _c(3); const { props, cond } = t0; let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.a = props.a; if (identity(cond)) { x.b = props.a.b; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md index 34979e9de9..c91cf94445 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md @@ -49,15 +49,15 @@ function Component(t0) { const $ = _c(8); const { cond, other } = t0; let x; - if ($[0] !== other || $[1] !== cond) { + if ($[0] !== cond || $[1] !== other) { x = makeObject_Primitives(); setProperty(x, { b: 3, other }, "a"); identity(x.a.b); if (!cond) { x.a = null; } - $[0] = other; - $[1] = cond; + $[0] = cond; + $[1] = other; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md index 0a1a423361..ff15f1a336 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md @@ -27,13 +27,13 @@ import { c as _c } from "react/compiler-runtime"; // Test that we can track non- function TestNonOverlappingDescendantTracked(props) { const $ = _c(4); let x; - if ($[0] !== props.a.x.y || $[1] !== props.a.c.x.y.z || $[2] !== props.b) { + if ($[0] !== props.a.c.x.y.z || $[1] !== props.a.x.y || $[2] !== props.b) { x = {}; x.a = props.a.x.y; x.b = props.b; x.c = props.a.c.x.y.z; - $[0] = props.a.x.y; - $[1] = props.a.c.x.y.z; + $[0] = props.a.c.x.y.z; + $[1] = props.a.x.y; $[2] = props.b; $[3] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md index da6912d35e..a2bd99e9a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md @@ -82,10 +82,10 @@ function Component(t0) { } const b = t3; let t4; - if ($[4] !== b || $[5] !== a) { + if ($[4] !== a || $[5] !== b) { t4 = { b, a }; - $[4] = b; - $[5] = a; + $[4] = a; + $[5] = b; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md index bba0b3f139..0089d20af2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md @@ -59,7 +59,7 @@ function Component(t0) { const serverTime = useServerTime(); let t1; let timestampLabel; - if ($[0] !== highlightedItem || $[1] !== serverTime || $[2] !== label) { + if ($[0] !== highlightedItem || $[1] !== label || $[2] !== serverTime) { const highlight = new Highlight(highlightedItem); const time = serverTime.get(); @@ -68,8 +68,8 @@ function Component(t0) { t1 = highlight.render(); $[0] = highlightedItem; - $[1] = serverTime; - $[2] = label; + $[1] = label; + $[2] = serverTime; $[3] = t1; $[4] = timestampLabel; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md index 9c21dc8400..ecdfa88b98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md @@ -66,10 +66,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md index 1b6e91a6fd..f9d725e0b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md @@ -65,10 +65,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md index 2dabc256f9..1ad6f6a047 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md @@ -71,10 +71,10 @@ function Foo() { const shouldCaptureObj = obj != null && CONST_TRUE; const t0 = shouldCaptureObj ? identity(obj) : null; let t1; - if ($[0] !== t0 || $[1] !== obj) { + if ($[0] !== obj || $[1] !== t0) { t1 = [t0, obj]; - $[0] = t0; - $[1] = obj; + $[0] = obj; + $[1] = t0; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md index 4921fd340b..d35cbe266f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md @@ -77,15 +77,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md index 80d046ec18..eda7a0cbfe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md @@ -74,15 +74,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md index 5eff1aa0fe..b5a7827728 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md @@ -21,13 +21,13 @@ function Component(listItem, thread) { let t0; let t1; let t2; - if ($[0] !== thread.threadType || $[1] !== listItem) { + if ($[0] !== listItem || $[1] !== thread.threadType) { const isFoo = isFooThread(thread.threadType); t1 = useBar; t2 = listItem; t0 = getBadgeText(listItem, isFoo); - $[0] = thread.threadType; - $[1] = listItem; + $[0] = listItem; + $[1] = thread.threadType; $[2] = t0; $[3] = t1; $[4] = t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md index 1d4a4b5d67..32cbbb2b91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md @@ -28,7 +28,7 @@ function V0(t0) { const { v1, v2 } = t0; const v5 = v1.v6?.v7; let t1; - if ($[0] !== v5 || $[1] !== v1 || $[2] !== v2) { + if ($[0] !== v1 || $[1] !== v2 || $[2] !== v5) { t1 = ( {v5 != null ? ( @@ -40,9 +40,9 @@ function V0(t0) { )} ); - $[0] = v5; - $[1] = v1; - $[2] = v2; + $[0] = v1; + $[1] = v2; + $[2] = v5; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md index cec64e8cf0..ab409b366d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md @@ -36,7 +36,7 @@ function useTest(t0) { const $ = _c(3); const { isNull, data } = t0; let t1; - if ($[0] !== isNull || $[1] !== data) { + if ($[0] !== data || $[1] !== isNull) { t1 = Builder.makeBuilder(isNull, "hello world") ?.push("1", 2) ?.push(3, { a: 4, b: 5, c: data }) @@ -47,8 +47,8 @@ function useTest(t0) { ) ?.push(7, "8") ?.push("8", Builder.makeBuilder(!isNull)?.push(9).vals)?.vals; - $[0] = isNull; - $[1] = data; + $[0] = data; + $[1] = isNull; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md index 31180b2236..fe7857a355 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md @@ -38,7 +38,7 @@ import { Stringify } from "shared-runtime"; function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.value.length || $[1] !== props.cond) { + if ($[0] !== props.cond || $[1] !== props.value.length) { const label = fbt._( { "*": "{number} bars", _1: "1 bar" }, [fbt._plural(props.value.length, "number")], @@ -51,8 +51,8 @@ function Component(props) { label={label.toString()} /> ) : null; - $[0] = props.value.length; - $[1] = props.cond; + $[0] = props.cond; + $[1] = props.value.length; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md index 73674e5fff..496d80d52b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md @@ -77,10 +77,10 @@ function Component(t0) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== result) { + if ($[4] !== result || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = result; + $[4] = result; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md index 2e9daceed7..1b49552bea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md @@ -26,8 +26,8 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); const item = props.item; - let t0; let baseVideos; + let t0; let thumbnails; if ($[0] !== item) { thumbnails = []; @@ -40,12 +40,12 @@ function Component(props) { } }); $[0] = item; - $[1] = t0; - $[2] = baseVideos; + $[1] = baseVideos; + $[2] = t0; $[3] = thumbnails; } else { - t0 = $[1]; - baseVideos = $[2]; + baseVideos = $[1]; + t0 = $[2]; thumbnails = $[3]; } t0 = undefined; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md index 9aa75b2162..aece9665c9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const [bar] = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md index ded1eb4006..3681e9c469 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const bar = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md index f0a46be168..3e66b20041 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const { bar } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md index 7c9c6a5028..1201a9a737 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md @@ -73,14 +73,14 @@ function foo(props) { } const header = t0; let y; - if ($[5] !== x || $[6] !== props.b || $[7] !== props.c) { + if ($[5] !== props.b || $[6] !== props.c || $[7] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[5] = x; - $[6] = props.b; - $[7] = props.c; + $[5] = props.b; + $[6] = props.c; + $[7] = x; $[8] = y; $[9] = x; } else { @@ -103,15 +103,15 @@ function foo(props) { } const content = t1; let t2; - if ($[13] !== header || $[14] !== content) { + if ($[13] !== content || $[14] !== header) { t2 = ( <> {header} {content} ); - $[13] = header; - $[14] = content; + $[13] = content; + $[14] = header; $[15] = t2; } else { t2 = $[15]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md index 36b1d4541a..143496678e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md @@ -53,30 +53,30 @@ import { c as _c } from "react/compiler-runtime"; // note: comments are for the // emitted function foo(props) { const $ = _c(14); - let x; let t0; + let x; if ($[0] !== props.a) { x = []; x.push(props.a); t0 =
{x}
; $[0] = props.a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const header = t0; let y; - if ($[3] !== x || $[4] !== props.b || $[5] !== props.c) { + if ($[3] !== props.b || $[4] !== props.c || $[5] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[3] = x; - $[4] = props.b; - $[5] = props.c; + $[3] = props.b; + $[4] = props.c; + $[5] = x; $[6] = y; $[7] = x; } else { @@ -99,15 +99,15 @@ function foo(props) { } const content = t1; let t2; - if ($[11] !== header || $[12] !== content) { + if ($[11] !== content || $[12] !== header) { t2 = ( <> {header} {content} ); - $[11] = header; - $[12] = content; + $[11] = content; + $[12] = header; $[13] = t2; } else { t2 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md index 5f10f44202..36a68d07c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md @@ -43,9 +43,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); - let text; let t0; let t1; + let text; if ($[0] !== statusName) { const { status, text: t2 } = foo(statusName); text = t2; @@ -54,13 +54,13 @@ function Component(statusName) { t1 = identity(bg); t0 = identity(color); $[0] = statusName; - $[1] = text; - $[2] = t0; - $[3] = t1; + $[1] = t0; + $[2] = t1; + $[3] = text; } else { - text = $[1]; - t0 = $[2]; - t1 = $[3]; + t0 = $[1]; + t1 = $[2]; + text = $[3]; } let t2; if ($[4] !== text) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md index e2cd53bd0d..d8e991dc46 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md @@ -46,9 +46,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); + let font; let t0; let text; - let font; if ($[0] !== statusName) { const { status, text: t1 } = foo(statusName); text = t1; @@ -58,13 +58,13 @@ function Component(statusName) { t0 = identity(color); $[0] = statusName; - $[1] = t0; - $[2] = text; - $[3] = font; + $[1] = font; + $[2] = t0; + $[3] = text; } else { - t0 = $[1]; - text = $[2]; - font = $[3]; + font = $[1]; + t0 = $[2]; + text = $[3]; } const bg = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..788109636b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -34,8 +34,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); - let y; let t0; + let y; if ($[0] !== props) { const x = []; bb0: switch (props.p0) { @@ -63,19 +63,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[4] !== child || $[5] !== y) { t1 = {child}; - $[4] = y; - $[5] = child; + $[4] = child; + $[5] = y; $[6] = t1; } else { t1 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..2628982655 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -29,8 +29,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(6); - let y; let t0; + let y; if ($[0] !== props) { const x = []; switch (props.p0) { @@ -45,19 +45,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[3] !== child || $[4] !== y) { t1 = {child}; - $[3] = y; - $[4] = child; + $[3] = child; + $[4] = y; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md index f106382d64..4864c51a1f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -64,8 +64,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -85,15 +85,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md index 77fd38aea1..c661094fdd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md @@ -55,7 +55,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -70,8 +70,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,15 +91,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md index d064a48b71..7ac6486b47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md @@ -18,10 +18,10 @@ function App() { const $ = _c(3); const [foo, bar] = useContext(MyContext); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md index f82af06866..3eac66304b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md @@ -22,10 +22,10 @@ function App() { const { foo } = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md index 573b6db231..4cca8b19d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md @@ -22,10 +22,10 @@ function App() { const [foo] = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md index 03ce7f97ba..f5a3916626 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md @@ -22,10 +22,10 @@ function App() { const { joe: t0, bar } = useContext(MyContext); const { foo } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md index 55387503cf..0888d67b2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md @@ -22,10 +22,10 @@ function App() { const foo = context.foo; const bar = context.bar; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md index 268fa8d7eb..2c27360c9f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md @@ -42,9 +42,9 @@ import { mutate, setProperty, throwErrorWithMessageIf } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { value, cond } = t0; - let y; let t1; - if ($[0] !== value || $[1] !== cond) { + let y; + if ($[0] !== cond || $[1] !== value) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { y = [value]; @@ -68,13 +68,13 @@ function useFoo(t0) { } y.push(x); } - $[0] = value; - $[1] = cond; - $[2] = y; - $[3] = t1; + $[0] = cond; + $[1] = value; + $[2] = t1; + $[3] = y; } else { - y = $[2]; - t1 = $[3]; + t1 = $[2]; + y = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md index 700df01c5c..5e57a46018 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md @@ -33,7 +33,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let x; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { try { const y = []; y.push(props.y); @@ -43,8 +43,8 @@ function Component(props) { e.push(props.e); x = e; } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md index c740c7d6b6..921d657e16 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md @@ -32,7 +32,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { try { @@ -46,8 +46,8 @@ function Component(props) { break bb0; } } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md index b04bd53458..562c0bc1c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md @@ -32,8 +32,8 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -47,11 +47,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md index af5f2ebfcf..71a59aba2f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md @@ -33,8 +33,8 @@ const { shallowCopy, throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -52,11 +52,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md index 54d5be2d6b..c3c45beb86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md @@ -78,10 +78,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -95,10 +95,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md index 072c6d03d9..4acbd2dfdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md @@ -76,10 +76,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -93,10 +93,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md index caa74267f3..5016b0c4df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md index a92abd4ca5..3f341fc665 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md index fbd13dc1b0..7fa86838e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md @@ -38,22 +38,22 @@ function component(a) { const f = typeof t.t; let t0; if ( - $[0] !== z || - $[1] !== p || - $[2] !== q || + $[0] !== e || + $[1] !== f || + $[2] !== m || $[3] !== n || - $[4] !== m || - $[5] !== e || - $[6] !== f + $[4] !== p || + $[5] !== q || + $[6] !== z ) { t0 = { z, p, q, n, m, e, f }; - $[0] = z; - $[1] = p; - $[2] = q; + $[0] = e; + $[1] = f; + $[2] = m; $[3] = n; - $[4] = m; - $[5] = e; - $[6] = f; + $[4] = p; + $[5] = q; + $[6] = z; $[7] = t0; } else { t0 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md index 663a6788f3..7f79cae4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md @@ -89,10 +89,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..d94a5e7e37 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -109,10 +109,10 @@ function Inner(props) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== output) { + if ($[4] !== output || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = output; + $[4] = output; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md index 99effce934..cf0093ea94 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md @@ -91,10 +91,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md index 1292ea1489..c3e115fa0d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md @@ -51,7 +51,7 @@ function Component(props) { } const exit = t0; let t1; - if ($[2] !== item.value || $[3] !== exit) { + if ($[2] !== exit || $[3] !== item.value) { t1 = () => { const cleanup = GlobalEventEmitter.addListener("onInput", () => { if (item.value) { @@ -60,8 +60,8 @@ function Component(props) { }); return () => cleanup.remove(); }; - $[2] = item.value; - $[3] = exit; + $[2] = exit; + $[3] = item.value; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md index b6a4187355..28d3e33359 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md @@ -62,13 +62,13 @@ function Component(props) { {w} ); - let condition = $[2] !== x || $[3] !== w; + let condition = $[2] !== w || $[3] !== x; if (!condition) { let old$t1 = $[4]; $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(7:10)"); } - $[2] = x; - $[3] = w; + $[2] = w; + $[3] = x; $[4] = t1; if (condition) { t1 = ( From b74b4009d7df803019c3649b9c89a04b6e49be37 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:11:44 -0500 Subject: [PATCH 086/422] [compiler][ez] Clean up pragma parsing for tests + playground Move environment config parsing for `inlineJsxTransform`, `lowerContextAccess`, and some dev-only options out of snap (test fixture). These should now be available for playground via `@inlineJsxTransform` and `lowerContextAccess`. Other small change: Changed zod fields from `nullish()` -> `nullable().default(null)`. [`nullish`](https://zod.dev/?id=nullish) fields accept `null | undefined` and default to `undefined`. We don't distinguish between null and undefined for any of these options, so let's only accept null + default to null. This also makes EnvironmentConfig in the playground more accurate. Previously, some fields just didn't show up as `prettyFormat({field: undefined})` does not print `field`. --- .../components/Editor/EditorImpl.tsx | 4 +- .../src/HIR/Environment.ts | 95 +++++++++++++------ .../src/HIR/index.ts | 2 +- ...codegen-emit-imports-same-source.expect.md | 4 +- .../codegen-emit-imports-same-source.js | 2 +- ...en-instrument-forget-gating-test.expect.md | 4 +- .../codegen-instrument-forget-gating-test.js | 2 +- .../codegen-instrument-forget-test.expect.md | 4 +- .../codegen-instrument-forget-test.js | 2 +- .../compiler/inline-jsx-transform.expect.md | 4 +- .../fixtures/compiler/inline-jsx-transform.js | 2 +- .../src/__tests__/parseConfigPragma-test.ts | 6 +- .../babel-plugin-react-compiler/src/index.ts | 2 +- compiler/packages/snap/src/compiler.ts | 74 +++------------ compiler/packages/snap/src/runner-worker.ts | 8 +- 15 files changed, 101 insertions(+), 114 deletions(-) diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 9911c15cd7..1bdd372ad2 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -14,7 +14,7 @@ import { CompilerErrorDetail, Effect, ErrorSeverity, - parseConfigPragma, + parseConfigPragmaForTests, ValueKind, runPlayground, type Hook, @@ -208,7 +208,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { try { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); - const config = parseConfigPragma(pragma); + const config = parseConfigPragmaForTests(pragma); for (const fn of parseFunctions(source, language)) { const id = withIdentifier(getFunctionIdentifier(fn)); diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 3e2b5597ac..1189f2e125 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -69,8 +69,8 @@ export const ExternalFunctionSchema = z.object({ export const InstrumentationSchema = z .object({ fn: ExternalFunctionSchema, - gating: ExternalFunctionSchema.nullish(), - globalGating: z.string().nullish(), + gating: ExternalFunctionSchema.nullable(), + globalGating: z.string().nullable(), }) .refine( opts => opts.gating != null || opts.globalGating != null, @@ -147,7 +147,7 @@ export type Hook = z.infer; */ const EnvironmentConfigSchema = z.object({ - customHooks: z.map(z.string(), HookSchema).optional().default(new Map()), + customHooks: z.map(z.string(), HookSchema).default(new Map()), /** * A function that, given the name of a module, can optionally return a description @@ -248,7 +248,7 @@ const EnvironmentConfigSchema = z.object({ * * The symbol configuration is set for backwards compatability with pre-React 19 transforms */ - inlineJsxTransform: ReactElementSymbolSchema.nullish(), + inlineJsxTransform: ReactElementSymbolSchema.nullable().default(null), /* * Enable validation of hooks to partially check that the component honors the rules of hooks. @@ -339,9 +339,9 @@ const EnvironmentConfigSchema = z.object({ * } * } */ - enableEmitFreeze: ExternalFunctionSchema.nullish(), + enableEmitFreeze: ExternalFunctionSchema.nullable().default(null), - enableEmitHookGuards: ExternalFunctionSchema.nullish(), + enableEmitHookGuards: ExternalFunctionSchema.nullable().default(null), /** * Enable instruction reordering. See InstructionReordering.ts for the details @@ -425,7 +425,7 @@ const EnvironmentConfigSchema = z.object({ * } * */ - enableEmitInstrumentForget: InstrumentationSchema.nullish(), + enableEmitInstrumentForget: InstrumentationSchema.nullable().default(null), // Enable validation of mutable ranges assertValidMutableRanges: z.boolean().default(false), @@ -464,8 +464,6 @@ const EnvironmentConfigSchema = z.object({ */ throwUnknownException__testonly: z.boolean().default(false), - enableSharedRuntime__testonly: z.boolean().default(false), - /** * Enables deps of a function epxression to be treated as conditional. This * makes sure we don't load a dep when it's a property (to check if it has @@ -503,7 +501,8 @@ const EnvironmentConfigSchema = z.object({ * computed one. This detects cases where rules of react violations may cause the * compiled code to behave differently than the original. */ - enableChangeDetectionForDebugging: ExternalFunctionSchema.nullish(), + enableChangeDetectionForDebugging: + ExternalFunctionSchema.nullable().default(null), /** * The react native re-animated library uses custom Babel transforms that @@ -543,7 +542,7 @@ const EnvironmentConfigSchema = z.object({ * * Here the variables `ref` and `myRef` will be typed as Refs. */ - enableTreatRefLikeIdentifiersAsRefs: z.boolean().nullable().default(false), + enableTreatRefLikeIdentifiersAsRefs: z.boolean().default(false), /* * If specified a value, the compiler lowers any calls to `useContext` to use @@ -565,12 +564,57 @@ const EnvironmentConfigSchema = z.object({ * const {foo, bar} = useCompiledContext(MyContext, (c) => [c.foo, c.bar]); * ``` */ - lowerContextAccess: ExternalFunctionSchema.nullish(), + lowerContextAccess: ExternalFunctionSchema.nullable().default(null), }); export type EnvironmentConfig = z.infer; -export function parseConfigPragma(pragma: string): EnvironmentConfig { +/** + * For test fixtures and playground only. + * + * Pragmas are straightforward to parse for boolean options (`:true` and + * `:false`). These are 'enabled' config values for non-boolean configs (i.e. + * what is used when parsing `:true`). + */ +const testComplexConfigDefaults: PartialEnvironmentConfig = { + validateNoCapitalizedCalls: [], + enableChangeDetectionForDebugging: { + source: 'react-compiler-runtime', + importSpecifierName: '$structuralCheck', + }, + enableEmitFreeze: { + source: 'react-compiler-runtime', + importSpecifierName: 'makeReadOnly', + }, + enableEmitInstrumentForget: { + fn: { + source: 'react-compiler-runtime', + importSpecifierName: 'useRenderCounter', + }, + gating: { + source: 'react-compiler-runtime', + importSpecifierName: 'shouldInstrument', + }, + globalGating: '__DEV__', + }, + enableEmitHookGuards: { + source: 'react-compiler-runtime', + importSpecifierName: '$dispatcherGuard', + }, + inlineJsxTransform: { + elementSymbol: 'react.transitional.element', + globalDevVar: 'DEV', + }, + lowerContextAccess: { + source: 'react-compiler-runtime', + importSpecifierName: 'useContext_withSelector', + }, +}; + +/** + * For snap test fixtures and playground only. + */ +export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { const maybeConfig: any = {}; // Get the defaults to programmatically check for boolean properties const defaultConfig = EnvironmentConfigSchema.parse({}); @@ -580,21 +624,12 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { continue; } const keyVal = token.slice(1); - let [key, val]: any = keyVal.split(':'); + let [key, val = undefined] = keyVal.split(':'); + const isSet = val === undefined || val === 'true'; - if (key === 'validateNoCapitalizedCalls') { - maybeConfig[key] = []; - continue; - } - - if ( - key === 'enableChangeDetectionForDebugging' && - (val === undefined || val === 'true') - ) { - maybeConfig[key] = { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }; + if (isSet && key in testComplexConfigDefaults) { + maybeConfig[key] = + testComplexConfigDefaults[key as keyof PartialEnvironmentConfig]; continue; } @@ -609,7 +644,6 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { props.push({type: 'name', name: elt}); } } - console.log([valSplit[0], props.map(x => x.name ?? '*').join('.')]); maybeConfig[key] = [[valSplit[0], props]]; } continue; @@ -620,11 +654,10 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { continue; } if (val === undefined || val === 'true') { - val = true; + maybeConfig[key] = true; } else { - val = false; + maybeConfig[key] = false; } - maybeConfig[key] = val; } const config = EnvironmentConfigSchema.safeParse(maybeConfig); diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts index a0fd782572..45267dd9a1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts @@ -17,7 +17,7 @@ export {buildReactiveScopeTerminalsHIR} from './BuildReactiveScopeTerminalsHIR'; export {computeDominatorTree, computePostDominatorTree} from './Dominator'; export { Environment, - parseConfigPragma, + parseConfigPragmaForTests, validateEnvironmentConfig, type EnvironmentConfig, type ExternalFunction, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md index dd67bcfbff..9a59b36cc0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitFreeze @instrumentForget +// @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { return foo(props.x); @@ -18,7 +18,7 @@ import { shouldInstrument, makeReadOnly, } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget +import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { if (__DEV__ && shouldInstrument) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js index 4edff1c3fc..bd66353319 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js @@ -1,4 +1,4 @@ -// @enableEmitFreeze @instrumentForget +// @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { return foo(props.x); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md index 4aa29992eb..fc9247344d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @instrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode(annotation) @gating function Bar(props) { 'use forget'; @@ -25,7 +25,7 @@ function Foo(props) { ```javascript import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) @gating +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js index 85fbd97ee7..dffb8ce795 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js @@ -1,4 +1,4 @@ -// @instrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode(annotation) @gating function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md index ba8ed5056b..b5da853b6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @instrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { 'use forget'; @@ -24,7 +24,7 @@ function Foo(props) { ```javascript import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js index 8947503277..2aef527e6b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js @@ -1,4 +1,4 @@ -// @instrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md index e657e36d36..f622b3a6fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableInlineJsxTransform +// @inlineJsxTransform function Parent({children, a: _a, b: _b, c: _c, ref}) { return
{children}
; @@ -76,7 +76,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c2 } from "react/compiler-runtime"; // @enableInlineJsxTransform +import { c as _c2 } from "react/compiler-runtime"; // @inlineJsxTransform function Parent(t0) { const $ = _c2(2); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js index bebb7ad53b..ca55cab4ff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js @@ -1,4 +1,4 @@ -// @enableInlineJsxTransform +// @inlineJsxTransform function Parent({children, a: _a, b: _b, c: _c, ref}) { return
{children}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts index 706563b33b..d634bd235f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts @@ -5,9 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import {parseConfigPragma, validateEnvironmentConfig} from '..'; +import {parseConfigPragmaForTests, validateEnvironmentConfig} from '..'; -describe('parseConfigPragma()', () => { +describe('parseConfigPragmaForTests()', () => { it('parses flags in various forms', () => { const defaultConfig = validateEnvironmentConfig({}); @@ -17,7 +17,7 @@ describe('parseConfigPragma()', () => { expect(defaultConfig.validateNoSetStateInPassiveEffects).toBe(false); expect(defaultConfig.validateNoSetStateInRender).toBe(true); - const config = parseConfigPragma( + const config = parseConfigPragmaForTests( '@enableUseTypeAnnotations @validateNoSetStateInPassiveEffects:true @validateNoSetStateInRender:false', ); expect(config).toEqual({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 256da2e5ed..60a7e7843c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -26,7 +26,7 @@ export { export { Effect, ValueKind, - parseConfigPragma, + parseConfigPragmaForTests, printHIR, validateEnvironmentConfig, type EnvironmentConfig, diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index f0ee88f06e..95af40d62a 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -21,10 +21,9 @@ import type { } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; import type { - EnvironmentConfig, Macro, MacroMethod, - parseConfigPragma as ParseConfigPragma, + parseConfigPragmaForTests as ParseConfigPragma, } from 'babel-plugin-react-compiler/src/HIR/Environment'; import * as HermesParser from 'hermes-parser'; import invariant from 'invariant'; @@ -37,6 +36,11 @@ export function parseLanguage(source: string): 'flow' | 'typescript' { return source.indexOf('@flow') !== -1 ? 'flow' : 'typescript'; } +/** + * Parse react compiler plugin + environment options from test fixture. Note + * that although this primarily uses `Environment:parseConfigPragma`, it also + * has test fixture specific (i.e. not applicable to playground) parsing logic. + */ function makePluginOptions( firstLine: string, parseConfigPragmaFn: typeof ParseConfigPragma, @@ -44,15 +48,11 @@ function makePluginOptions( ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { let gating = null; - let enableEmitInstrumentForget = null; - let enableEmitFreeze = null; - let enableEmitHookGuards = null; let compilationMode: CompilationMode = 'all'; let panicThreshold: PanicThresholdOptions = 'all_errors'; let hookPattern: string | null = null; // TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false let validatePreserveExistingMemoizationGuarantees = false; - let enableChangeDetectionForDebugging = null; let customMacros: null | Array = null; let validateBlocklistedImports = null; let target = '19' as const; @@ -78,31 +78,6 @@ function makePluginOptions( importSpecifierName: 'isForgetEnabled_Fixtures', }; } - if (firstLine.includes('@instrumentForget')) { - enableEmitInstrumentForget = { - fn: { - source: 'react-compiler-runtime', - importSpecifierName: 'useRenderCounter', - }, - gating: { - source: 'react-compiler-runtime', - importSpecifierName: 'shouldInstrument', - }, - globalGating: '__DEV__', - }; - } - if (firstLine.includes('@enableEmitFreeze')) { - enableEmitFreeze = { - source: 'react-compiler-runtime', - importSpecifierName: 'makeReadOnly', - }; - } - if (firstLine.includes('@enableEmitHookGuards')) { - enableEmitHookGuards = { - source: 'react-compiler-runtime', - importSpecifierName: '$dispatcherGuard', - }; - } const targetMatch = /@target="([^"]+)"/.exec(firstLine); if (targetMatch) { @@ -132,16 +107,18 @@ function makePluginOptions( ignoreUseNoForget = true; } + /** + * Snap currently runs all fixtures without `validatePreserveExistingMemo` as + * most fixtures are interested in compilation output, not whether the + * compiler was able to preserve existing memo. + * + * TODO: flip the default. `useMemo` is rare in test fixtures -- fixtures that + * use useMemo should be explicit about whether this flag is enabled + */ if (firstLine.includes('@validatePreserveExistingMemoizationGuarantees')) { validatePreserveExistingMemoizationGuarantees = true; } - if (firstLine.includes('@enableChangeDetectionForDebugging')) { - enableChangeDetectionForDebugging = { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }; - } const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine); if ( hookPatternMatch && @@ -197,22 +174,6 @@ function makePluginOptions( .filter(s => s.length > 0); } - let lowerContextAccess = null; - if (firstLine.includes('@lowerContextAccess')) { - lowerContextAccess = { - source: 'react-compiler-runtime', - importSpecifierName: 'useContext_withSelector', - }; - } - - let inlineJsxTransform: EnvironmentConfig['inlineJsxTransform'] = null; - if (firstLine.includes('@enableInlineJsxTransform')) { - inlineJsxTransform = { - elementSymbol: 'react.transitional.element', - globalDevVar: 'DEV', - }; - } - let logs: Array<{filename: string | null; event: LoggerEvent}> = []; let logger: Logger | null = null; if (firstLine.includes('@logger')) { @@ -232,17 +193,10 @@ function makePluginOptions( ValueKindEnum, }), customMacros, - enableEmitFreeze, - enableEmitInstrumentForget, - enableEmitHookGuards, assertValidMutableRanges: true, - enableSharedRuntime__testonly: true, hookPattern, validatePreserveExistingMemoizationGuarantees, - enableChangeDetectionForDebugging, - lowerContextAccess, validateBlocklistedImports, - inlineJsxTransform, }, compilationMode, logger, diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index 9447b2cddc..f05757d3df 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -7,7 +7,7 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {PluginObj} from '@babel/core'; -import type {parseConfigPragma as ParseConfigPragma} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/HIR/Environment'; import {TransformResult, transformFixtureInput} from './compiler'; import { COMPILER_PATH, @@ -65,8 +65,8 @@ async function compile( COMPILER_INDEX_PATH, ); const {toggleLogging} = require(LOGGER_PATH); - const {parseConfigPragma} = require(PARSE_CONFIG_PRAGMA_PATH) as { - parseConfigPragma: typeof ParseConfigPragma; + const {parseConfigPragmaForTests} = require(PARSE_CONFIG_PRAGMA_PATH) as { + parseConfigPragmaForTests: typeof ParseConfigPragma; }; // only try logging if we filtered out all but one fixture, @@ -75,7 +75,7 @@ async function compile( const result = await transformFixtureInput( input, fixturePath, - parseConfigPragma, + parseConfigPragmaForTests, BabelPluginReactCompiler, includeEvaluator, EffectEnum, From 355296e4c917ff060f96563f9dc774e5d51b86bf Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:19:59 -0500 Subject: [PATCH 087/422] [compiler][be] Stabilize compiler output: sort deps and decls by name All dependencies and declarations of a reactive scope can be reordered to scope start/end. i.e. generated code does not depend on conditional short-circuiting logic as dependencies are inferred to have no side effects. Sorting these by name helps us get higher signal compilation snapshot diffs when upgrading the compiler and testing PRs internally at Meta --- .../ReactiveScopes/CodegenReactiveFunction.ts | 51 ++++++++++++++++++- ...ng-primitive-as-dep-nested-scope.expect.md | 6 +-- .../compiler/array-at-effect.expect.md | 6 +-- .../array-expression-spread.expect.md | 6 +-- .../compiler/array-property-call.expect.md | 10 ++-- .../bug-invalid-phi-as-dependency.expect.md | 6 +-- ...nstruction-hoisted-sequence-expr.expect.md | 10 ++-- ...fun-alias-captured-mutate-2-iife.expect.md | 6 +-- ...ring-fun-alias-captured-mutate-2.expect.md | 6 +-- ...alias-captured-mutate-arr-2-iife.expect.md | 6 +-- ...-fun-alias-captured-mutate-arr-2.expect.md | 6 +-- ...c-alias-captured-mutate-arr-iife.expect.md | 6 +-- ...g-func-alias-captured-mutate-arr.expect.md | 6 +-- ...-func-alias-captured-mutate-iife.expect.md | 6 +-- ...uring-func-alias-captured-mutate.expect.md | 6 +-- ...turing-function-member-expr-call.expect.md | 6 +-- .../fixtures/compiler/component.expect.md | 12 ++--- .../compiler/computed-call-spread.expect.md | 8 +-- .../compiler/dependencies-outputs.expect.md | 6 +-- .../destructure-in-branch-ssa.expect.md | 8 +-- ...g-same-property-identifier-names.expect.md | 6 +-- .../fixtures/compiler/destructuring.expect.md | 34 ++++++------- ...er-declaration-of-previous-scope.expect.md | 10 ++-- .../existing-variables-with-c-name.expect.md | 6 +-- .../compiler/fast-refresh-reloading.expect.md | 6 +-- ...-mutable-range-destructured-prop.expect.md | 6 +-- ...btparam-with-jsx-element-content.expect.md | 6 +-- ...oop-with-value-block-initializer.expect.md | 6 +-- ...onmutating-loop-local-collection.expect.md | 6 +-- ...pression-prototype-call-mutating.expect.md | 6 +-- ...unctionexpr-conditional-access-2.expect.md | 6 +-- .../functionexpr–conditional-access.expect.md | 6 +-- ...bals-dont-resolve-local-useState.expect.md | 6 +-- .../fixtures/compiler/hook-noAlias.expect.md | 6 +-- .../compiler/hooks-with-prefix.expect.md | 6 +-- ...incompatible-destructuring-kinds.expect.md | 14 ++--- ...-promoted-to-outer-scope-dynamic.expect.md | 20 ++++---- ...jsx-outlining-child-stored-in-id.expect.md | 12 ++--- .../jsx-outlining-jsx-stored-in-id.expect.md | 18 +++---- .../jsx-outlining-separate-nested.expect.md | 22 ++++---- .../compiler/jsx-outlining-simple.expect.md | 18 +++---- ...-tag-evaluation-order-non-global.expect.md | 16 +++--- .../lambda-capture-returned-alias.expect.md | 6 +-- .../lower-context-acess-multiple.expect.md | 6 +-- .../lower-context-selector-simple.expect.md | 6 +-- ...ge-consecutive-scopes-reordering.expect.md | 6 +-- .../compiler/method-call-computed.expect.md | 8 +-- .../compiler/method-call-fn-call.expect.md | 8 +-- .../fixtures/compiler/method-call.expect.md | 6 +-- ...pture-in-unsplittable-memo-block.expect.md | 10 ++-- .../object-shorthand-method-nested.expect.md | 6 +-- ...al-member-expression-as-memo-dep.expect.md | 6 +-- ...ession-single-with-unconditional.expect.md | 6 +-- ...ptional-member-expression-single.expect.md | 6 +-- ...ession-with-conditional-optional.expect.md | 12 ++--- ...mber-expression-with-conditional.expect.md | 12 ++--- ...rly-return-within-reactive-scope.expect.md | 10 ++-- ...Callback-in-other-reactive-block.expect.md | 8 +-- ...k-reordering-deplist-controlflow.expect.md | 16 +++--- ...useMemo-conditional-access-alloc.expect.md | 6 +-- ...eMemo-conditional-access-noAlloc.expect.md | 6 +-- .../useMemo-in-other-reactive-block.expect.md | 8 +-- ...-reordering-depslist-controlflow.expect.md | 6 +-- .../primitive-as-dep-nested-scope.expect.md | 6 +-- ...signed-loop-force-scopes-enabled.expect.md | 8 +-- ...rly-return-within-reactive-scope.expect.md | 8 +-- ...rly-return-within-reactive-scope.expect.md | 8 +-- .../iife-return-modified-later-phi.expect.md | 6 +-- ...al-member-expression-as-memo-dep.expect.md | 6 +-- ...ession-single-with-unconditional.expect.md | 6 +-- ...ptional-member-expression-single.expect.md | 6 +-- ...rly-return-within-reactive-scope.expect.md | 18 +++---- ...hi-type-inference-property-store.expect.md | 6 +-- ...r-function-cond-access-local-var.expect.md | 6 +-- ...function-cond-access-not-hoisted.expect.md | 6 +-- ...n-uncond-access-hoists-other-dep.expect.md | 6 +-- ...uncond-optional-hoists-other-dep.expect.md | 12 ++--- .../infer-objectmethod-cond-access.expect.md | 6 +-- .../memberexpr-join-optional-chain2.expect.md | 6 +-- .../promote-uncond.expect.md | 8 +-- ...a-renaming-unconditional-ternary.expect.md | 8 +-- .../switch-non-final-default.expect.md | 16 +++--- .../switch.expect.md | 16 +++--- ...value-modified-in-catch-escaping.expect.md | 6 +-- ...atch-try-value-modified-in-catch.expect.md | 6 +-- .../useMemo-multiple-if-else.expect.md | 16 +++--- ...-analysis-interleaved-reactivity.expect.md | 6 +-- ...ve-via-mutation-of-computed-load.expect.md | 12 ++--- ...ve-via-mutation-of-property-load.expect.md | 6 +-- .../reassignment-separate-scopes.expect.md | 16 +++--- ...eactive-cond-deps-break-in-scope.expect.md | 6 +-- ...active-cond-deps-return-in-scope.expect.md | 16 +++--- ...function-cond-access-not-hoisted.expect.md | 6 +-- .../hoist-deps-diff-ssa-instance.expect.md | 6 +-- .../jump-poisoned/break-in-scope.expect.md | 6 +-- .../loop-break-in-scope.expect.md | 6 +-- ...e-if-nonexhaustive-poisoned-deps.expect.md | 10 ++-- ...-if-nonexhaustive-poisoned-deps1.expect.md | 10 ++-- .../jump-poisoned/return-in-scope.expect.md | 16 +++--- .../return-poisons-outer-scope.expect.md | 10 ++-- ...p-target-within-scope-loop-break.expect.md | 6 +-- ...e-if-exhaustive-nonpoisoned-deps.expect.md | 10 ++-- ...-if-exhaustive-nonpoisoned-deps1.expect.md | 10 ++-- .../memberexpr-join-optional-chain2.expect.md | 6 +-- .../promote-uncond.expect.md | 6 +-- ...duce-if-exhaustive-poisoned-deps.expect.md | 18 +++---- .../subpath-order1.expect.md | 6 +-- .../superpath-order1.expect.md | 6 +-- .../uncond-access-in-mutable-range.expect.md | 6 +-- .../uncond-nonoverlap-descendant.expect.md | 6 +-- .../reordering-across-blocks.expect.md | 6 +-- ...ed-property-load-for-method-call.expect.md | 6 +-- ...uned-scope-leaks-value-via-alias.expect.md | 6 +-- ...invalid-pruned-scope-leaks-value.expect.md | 6 +-- ...o-invalid-reactivity-value-block.expect.md | 6 +-- ...lack-of-phi-types-explicit-types.expect.md | 6 +-- ...ng-memoization-lack-of-phi-types.expect.md | 6 +-- .../repro-no-value-for-temporary.expect.md | 6 +-- ...ro-propagate-type-of-ternary-jsx.expect.md | 8 +-- ...epro-slow-validate-preserve-memo.expect.md | 6 +-- ...erge-overlapping-reactive-scopes.expect.md | 6 +-- ...ble-code-early-return-in-useMemo.expect.md | 6 +-- .../fixtures/compiler/repro.expect.md | 10 ++-- .../rest-param-with-array-pattern.expect.md | 6 +-- .../rest-param-with-identifier.expect.md | 6 +-- ...param-with-object-spread-pattern.expect.md | 6 +-- ...s-dep-and-redeclare-maybe-frozen.expect.md | 14 ++--- ...me-variable-as-dep-and-redeclare.expect.md | 24 ++++----- ...assignment-to-scope-declarations.expect.md | 14 ++--- ...ixed-local-and-scope-declaration.expect.md | 14 ++--- .../switch-non-final-default.expect.md | 16 +++--- .../fixtures/compiler/switch.expect.md | 16 +++--- .../todo.jsx-outlining-children.expect.md | 12 ++--- ...odo.jsx-outlining-duplicate-prop.expect.md | 12 ++--- ...ntext-access-array-destructuring.expect.md | 6 +-- ...text-access-destructure-multiple.expect.md | 6 +-- ...r-context-access-mixed-array-obj.expect.md | 6 +-- ...text-access-nested-destructuring.expect.md | 6 +-- ...wer-context-access-property-load.expect.md | 6 +-- .../try-catch-in-nested-scope.expect.md | 16 +++--- ...value-modified-in-catch-escaping.expect.md | 6 +-- ...atch-try-value-modified-in-catch.expect.md | 6 +-- .../try-catch-with-catch-param.expect.md | 10 ++-- .../compiler/try-catch-with-return.expect.md | 10 ++-- ...type-provider-log-default-import.expect.md | 12 ++--- .../compiler/type-provider-log.expect.md | 12 ++--- ...r-store-capture-namespace-import.expect.md | 20 ++++---- .../type-provider-store-capture.expect.md | 20 ++++---- .../fixtures/compiler/unary-expr.expect.md | 24 ++++----- .../use-operator-call-expression.expect.md | 6 +-- .../use-operator-conditional.expect.md | 6 +-- .../use-operator-method-call.expect.md | 6 +-- .../useEffect-nested-lambdas.expect.md | 6 +-- .../useState-unpruned-dependency.expect.md | 6 +-- 154 files changed, 732 insertions(+), 685 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 297c771254..167db6dede 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -34,6 +34,7 @@ import { ReactiveInstruction, ReactiveScope, ReactiveScopeBlock, + ReactiveScopeDeclaration, ReactiveScopeDependency, ReactiveTerminal, ReactiveValue, @@ -572,7 +573,8 @@ function codegenReactiveScope( const changeExpressions: Array = []; const changeExpressionComments: Array = []; const outputComments: Array = []; - for (const dep of scope.dependencies) { + + for (const dep of [...scope.dependencies].sort(compareScopeDependency)) { const index = cx.nextCacheIndex; changeExpressionComments.push(printDependencyComment(dep)); const comparison = t.binaryExpression( @@ -615,7 +617,10 @@ function codegenReactiveScope( ); } let firstOutputIndex: number | null = null; - for (const [, {identifier}] of scope.declarations) { + + for (const [, {identifier}] of [...scope.declarations].sort(([, a], [, b]) => + compareScopeDeclaration(a, b), + )) { const index = cx.nextCacheIndex; if (firstOutputIndex === null) { firstOutputIndex = index; @@ -2566,3 +2571,45 @@ function convertIdentifier(identifier: Identifier): t.Identifier { ); return t.identifier(identifier.name.value); } + +function compareScopeDependency( + a: ReactiveScopeDependency, + b: ReactiveScopeDependency, +): number { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected named identifier for dependency', + loc: a.identifier.loc, + }, + ); + const aName = [ + a.identifier.name.value, + ...a.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`), + ].join('.'); + const bName = [ + b.identifier.name.value, + ...b.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`), + ].join('.'); + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; +} + +function compareScopeDeclaration( + a: ReactiveScopeDeclaration, + b: ReactiveScopeDeclaration, +): number { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected named identifier for declaration', + loc: a.identifier.loc, + }, + ); + const aName = a.identifier.name.value; + const bName = b.identifier.name.value; + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md index cb550b4230..6702b26e92 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md @@ -47,7 +47,7 @@ import { identity, mutate, setProperty } from "shared-runtime"; function AllocatingPrimitiveAsDepNested(props) { const $ = _c(5); let t0; - if ($[0] !== props.b || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.b) { const x = {}; mutate(x); const t1 = identity(props.b) + 1; @@ -62,8 +62,8 @@ function AllocatingPrimitiveAsDepNested(props) { const y = t2; setProperty(x, props.a); t0 = [x, y]; - $[0] = props.b; - $[1] = props.a; + $[0] = props.a; + $[1] = props.b; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md index 3aa51ba6d6..a8bad51215 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md @@ -41,7 +41,7 @@ function ArrayAtTest(props) { } const arr = t1; let t2; - if ($[4] !== props.y || $[5] !== arr) { + if ($[4] !== arr || $[5] !== props.y) { let t3; if ($[7] !== props.y) { t3 = bar(props.y); @@ -51,8 +51,8 @@ function ArrayAtTest(props) { t3 = $[8]; } t2 = arr.at(t3); - $[4] = props.y; - $[5] = arr; + $[4] = arr; + $[5] = props.y; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md index 46ae949238..f3af7efcf6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md @@ -22,10 +22,10 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.foo || $[1] !== props.bar) { + if ($[0] !== props.bar || $[1] !== props.foo) { t0 = [0, ...props.foo, null, ...props.bar, "z"]; - $[0] = props.foo; - $[1] = props.bar; + $[0] = props.bar; + $[1] = props.foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md index 7aa47c5803..6618be4a6c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md @@ -24,18 +24,18 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(11); - let t0; let a; + let t0; if ($[0] !== props.a || $[1] !== props.b) { a = [props.a, props.b, "hello"]; t0 = a.push(42); $[0] = props.a; $[1] = props.b; - $[2] = t0; - $[3] = a; + $[2] = a; + $[3] = t0; } else { - t0 = $[2]; - a = $[3]; + a = $[2]; + t0 = $[3]; } const x = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md index 32e2c9fd64..09d2d8800b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md @@ -70,10 +70,10 @@ function Component() { throw new Error("invariant broken"); } let t1; - if ($[1] !== obj || $[2] !== boxedInner) { + if ($[1] !== boxedInner || $[2] !== obj) { t1 = ; - $[1] = obj; - $[2] = boxedInner; + $[1] = boxedInner; + $[2] = obj; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md index dcca6cddd8..4ffe0fcb6a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md @@ -57,16 +57,16 @@ import { identity, mutate } from "shared-runtime"; */ function Component(props) { const $ = _c(8); - let t0; let key; + let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { key = {}; t0 = (mutate(key), key); - $[0] = t0; - $[1] = key; + $[0] = key; + $[1] = t0; } else { - t0 = $[0]; - key = $[1]; + key = $[0]; + t0 = $[1]; } const tmp = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md index 65ab9c277c..5e0b32709b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md @@ -32,7 +32,7 @@ import { mutate } from "shared-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md index 170f68bade..f9ce3f2e98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md index e315cd401d..81737a1ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md index 7371f3d27a..38590d1559 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md index cc41adcbbc..4882aa822f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md index 34f6a55740..7c94c33e49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md index b2d7048756..60493dd25a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..14532562fb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md index 51679299fe..cab9c9a500 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md @@ -46,10 +46,10 @@ function component(t0) { } const hide = t2; let t3; - if ($[4] !== poke || $[5] !== hide) { + if ($[4] !== hide || $[5] !== poke) { t3 = ; - $[4] = poke; - $[5] = hide; + $[4] = hide; + $[5] = poke; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md index 80d6e6df8c..c6037fd2bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md @@ -46,7 +46,7 @@ function Component(props) { const items = props.items; const maxItems = props.maxItems; let renderedItems; - if ($[0] !== maxItems || $[1] !== items) { + if ($[0] !== items || $[1] !== maxItems) { renderedItems = []; const seen = new Set(); const max = Math.max(0, maxItems); @@ -62,8 +62,8 @@ function Component(props) { break; } } - $[0] = maxItems; - $[1] = items; + $[0] = items; + $[1] = maxItems; $[2] = renderedItems; } else { renderedItems = $[2]; @@ -79,15 +79,15 @@ function Component(props) { t0 = $[4]; } let t1; - if ($[5] !== t0 || $[6] !== renderedItems) { + if ($[5] !== renderedItems || $[6] !== t0) { t1 = (
{t0} {renderedItems}
); - $[5] = t0; - $[6] = renderedItems; + $[5] = renderedItems; + $[6] = t0; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md index cb20d97cb7..0329450b13 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md @@ -16,11 +16,11 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.method || $[1] !== props.a || $[2] !== props.b) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.method) { t0 = foo[props.method](...props.a, null, ...props.b); - $[0] = props.method; - $[1] = props.a; - $[2] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.method; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md index 6fc686cb19..f0f9911c07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md @@ -41,7 +41,7 @@ function foo(a, b) { x = $[1]; } let y; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { y = []; if (x.length) { y.push(x); @@ -49,8 +49,8 @@ function foo(a, b) { if (b) { y.push(b); } - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = y; } else { y = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md index d65082cbc8..b159789106 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md @@ -61,11 +61,11 @@ function useFoo(props) { z = $[4]; } let t0; - if ($[5] !== x || $[6] !== y || $[7] !== myList) { + if ($[5] !== myList || $[6] !== x || $[7] !== y) { t0 = { x, y, myList }; - $[5] = x; - $[6] = y; - $[7] = myList; + $[5] = myList; + $[6] = x; + $[7] = y; $[8] = t0; } else { t0 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md index b86498b922..3e1c4771ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md @@ -41,10 +41,10 @@ function Component(props) { } const sameName = t1; let t2; - if ($[2] !== sameName || $[3] !== renamed) { + if ($[2] !== renamed || $[3] !== sameName) { t2 = [sameName, renamed]; - $[2] = sameName; - $[3] = renamed; + $[2] = renamed; + $[3] = sameName; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md index c175cc558c..f292e83e16 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md @@ -36,45 +36,45 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(18); - let t0; let d; let h; + let t0; if ($[0] !== a) { [d, t0, ...h] = a; $[0] = a; - $[1] = t0; - $[2] = d; - $[3] = h; + $[1] = d; + $[2] = h; + $[3] = t0; } else { - t0 = $[1]; - d = $[2]; - h = $[3]; + d = $[1]; + h = $[2]; + t0 = $[3]; } const [t1] = t0; - let t2; let g; + let t2; if ($[4] !== t1) { ({ e: t2, ...g } = t1); $[4] = t1; - $[5] = t2; - $[6] = g; + $[5] = g; + $[6] = t2; } else { - t2 = $[5]; - g = $[6]; + g = $[5]; + t2 = $[6]; } const { f } = t2; const { l: t3, p } = b; const { m: t4 } = t3; - let t5; let o; + let t5; if ($[7] !== t4) { [t5, ...o] = t4; $[7] = t4; - $[8] = t5; - $[9] = o; + $[8] = o; + $[9] = t5; } else { - t5 = $[8]; - o = $[9]; + o = $[8]; + t5 = $[9]; } const [n] = t5; let t6; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md index 29780eb76c..ce5bfda644 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md @@ -53,8 +53,8 @@ import { ValidateMemoization } from "shared-runtime"; function Component(t0) { const $ = _c(25); const { a, b, c } = t0; - let y; let x; + let y; if ($[0] !== a || $[1] !== b || $[2] !== c) { x = []; if (a) { @@ -73,11 +73,11 @@ function Component(t0) { $[0] = a; $[1] = b; $[2] = c; - $[3] = y; - $[4] = x; + $[3] = x; + $[4] = y; } else { - y = $[3]; - x = $[4]; + x = $[3]; + y = $[4]; } let t1; if ($[7] !== y) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md index 0d671a3de2..5cde3bde23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md @@ -61,10 +61,10 @@ function Component(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== array) { + if ($[4] !== array || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = array; + $[4] = array; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md index ecd03a0b10..4175d23fda 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md @@ -59,10 +59,10 @@ function Component(props) { t3 = $[4]; } let t4; - if ($[5] !== t3 || $[6] !== doubled) { + if ($[5] !== doubled || $[6] !== t3) { t4 = ; - $[5] = t3; - $[6] = doubled; + $[5] = doubled; + $[6] = t3; $[7] = t4; } else { t4 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md index d56f7a98ad..9bb651aa67 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md @@ -61,10 +61,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== el) { + if ($[4] !== el || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = el; + $[4] = el; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md index d58c25b510..56ffb70cb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md @@ -32,7 +32,7 @@ function Component(t0) { const $ = _c(4); const { name, data, icon } = t0; let t1; - if ($[0] !== name || $[1] !== icon || $[2] !== data) { + if ($[0] !== data || $[1] !== icon || $[2] !== name) { t1 = ( {fbt._( @@ -61,9 +61,9 @@ function Component(t0) { )} ); - $[0] = name; + $[0] = data; $[1] = icon; - $[2] = data; + $[2] = name; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md index 24f2e545cc..6ef73de8b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-value-block-initializer.expect.md @@ -67,7 +67,7 @@ const TOTAL = 10; function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.start || $[1] !== props.items) { + if ($[0] !== props.items || $[1] !== props.start) { const items = []; for (let i = props.start ?? 0; i < props.items.length; i++) { const item = props.items[i]; @@ -75,8 +75,8 @@ function Component(props) { } t0 =
{items}
; - $[0] = props.start; - $[1] = props.items; + $[0] = props.items; + $[1] = props.start; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md index cff8c5bd13..4abe630044 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md @@ -91,10 +91,10 @@ function Component(t0) { t5 = $[9]; } let t6; - if ($[10] !== x || $[11] !== b) { + if ($[10] !== b || $[11] !== x) { t6 = [x, b]; - $[10] = x; - $[11] = b; + $[10] = b; + $[11] = x; $[12] = t6; } else { t6 = $[12]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md index be59673c15..9888e96222 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md @@ -59,10 +59,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== a_0) { + if ($[4] !== a_0 || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = a_0; + $[4] = a_0; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..32498e1379 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -36,10 +36,10 @@ function Component(t0) { } const f = t1; let t2; - if ($[2] !== props || $[3] !== f) { + if ($[2] !== f || $[3] !== props) { t2 = props == null ? _temp : f; - $[2] = props; - $[3] = f; + $[2] = f; + $[3] = props; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..4a62bf6f24 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -36,10 +36,10 @@ function Component(props) { } const getLength = t0; let t1; - if ($[2] !== props.bar || $[3] !== getLength) { + if ($[2] !== getLength || $[3] !== props.bar) { t1 = props.bar && getLength(); - $[2] = props.bar; - $[3] = getLength; + $[2] = getLength; + $[3] = props.bar; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md index 7548a3b639..be7f3f1bd2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md @@ -56,10 +56,10 @@ function Component() { t0 = $[1]; } let t1; - if ($[2] !== t0 || $[3] !== state) { + if ($[2] !== state || $[3] !== t0) { t1 =
{state}
; - $[2] = t0; - $[3] = state; + $[2] = state; + $[3] = t0; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md index 96ccd1e2f1..329d57a035 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md @@ -41,10 +41,10 @@ function Component(props) { console.log(props); }, [props.a]); let t1; - if ($[2] !== x || $[3] !== item) { + if ($[2] !== item || $[3] !== x) { t1 = [x, item]; - $[2] = x; - $[3] = item; + $[2] = item; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md index f7e02a53f1..085df625f5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md @@ -73,15 +73,15 @@ function Component() { t1 = $[4]; } let t3; - if ($[5] !== t2 || $[6] !== json) { + if ($[5] !== json || $[6] !== t2) { t3 = (
{t2} {json}
); - $[5] = t2; - $[6] = json; + $[5] = json; + $[6] = t2; $[7] = t3; } else { t3 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md index 970cc50f03..8afc59a80b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md @@ -29,22 +29,22 @@ import { Stringify } from "shared-runtime"; function Component(t0) { const $ = _c(4); - let t1; let a; let b; + let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { a = "a"; const [t2, t3] = [null, null]; t1 = t3; a = t2; - $[0] = t1; - $[1] = a; - $[2] = b; + $[0] = a; + $[1] = b; + $[2] = t1; } else { - t1 = $[0]; - a = $[1]; - b = $[2]; + a = $[0]; + b = $[1]; + t1 = $[2]; } b = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md index becc4066e7..735462657f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md @@ -27,10 +27,10 @@ function Component(props) { const $ = _c(15); const item = useFragment(FRAGMENT, props.item); useFreeze(item); - let t0; let T0; - let t1; let T1; + let t0; + let t1; if ($[0] !== item) { const count = new MaybeMutable(item); @@ -44,15 +44,15 @@ function Component(props) { } t0 = maybeMutate(count); $[0] = item; - $[1] = t0; - $[2] = T0; - $[3] = t1; - $[4] = T1; + $[1] = T0; + $[2] = T1; + $[3] = t0; + $[4] = t1; } else { - t0 = $[1]; - T0 = $[2]; - t1 = $[3]; - T1 = $[4]; + T0 = $[1]; + T1 = $[2]; + t0 = $[3]; + t1 = $[4]; } let t2; if ($[6] !== t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..b84229156b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -83,10 +83,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -98,15 +98,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md index 496282e1ef..7fca963134 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md @@ -52,7 +52,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -66,8 +66,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -94,10 +94,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -109,15 +109,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md index 7f86546cd4..9d2b254c06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md @@ -60,7 +60,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -73,8 +73,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -117,7 +117,7 @@ function _temp(t0) { t3 = $[5]; } let t4; - if ($[6] !== x || $[7] !== t1 || $[8] !== t2 || $[9] !== t3) { + if ($[6] !== t1 || $[7] !== t2 || $[8] !== t3 || $[9] !== x) { t4 = ( {t1} @@ -125,10 +125,10 @@ function _temp(t0) { {t3} ); - $[6] = x; - $[7] = t1; - $[8] = t2; - $[9] = t3; + $[6] = t1; + $[7] = t2; + $[8] = t3; + $[9] = x; $[10] = t4; } else { t4 = $[10]; @@ -140,15 +140,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md index 9e268227a2..09323f5ac6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -63,8 +63,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,10 +91,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -106,15 +106,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md index b1dfbc61c3..d46ce53bb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md @@ -53,23 +53,23 @@ function maybeMutate(x) {} function Component(props) { const $ = _c(11); - let Tag; let T0; + let Tag; let t0; - if ($[0] !== props.component || $[1] !== props.alternateComponent) { + if ($[0] !== props.alternateComponent || $[1] !== props.component) { const maybeMutable = new MaybeMutable(); Tag = props.component; T0 = Tag; t0 = ((Tag = props.alternateComponent), maybeMutate(maybeMutable)); - $[0] = props.component; - $[1] = props.alternateComponent; - $[2] = Tag; - $[3] = T0; + $[0] = props.alternateComponent; + $[1] = props.component; + $[2] = T0; + $[3] = Tag; $[4] = t0; } else { - Tag = $[2]; - T0 = $[3]; + T0 = $[2]; + Tag = $[3]; t0 = $[4]; } let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md index e172ee1039..bac21217c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md @@ -44,7 +44,7 @@ function CaptureNotMutate(props) { } const idx = t0; let aliasedElement; - if ($[2] !== props.el || $[3] !== idx) { + if ($[2] !== idx || $[3] !== props.el) { const element = bar(props.el); const fn = function () { @@ -54,8 +54,8 @@ function CaptureNotMutate(props) { aliasedElement = fn(); mutate(aliasedElement); - $[2] = props.el; - $[3] = idx; + $[2] = idx; + $[3] = props.el; $[4] = aliasedElement; } else { aliasedElement = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md index 47c7a2d743..af9b1df36a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md @@ -21,10 +21,10 @@ function App() { const { foo } = useContext_withSelector(MyContext, _temp); const { bar } = useContext_withSelector(MyContext, _temp2); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md index 0b12c2e250..d13682467b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md @@ -19,10 +19,10 @@ function App() { const $ = _c(3); const { foo, bar } = useContext_withSelector(MyContext, _temp); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md index 5bf1f2cf4d..e5a9081137 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md @@ -60,7 +60,7 @@ function Component() { t2 = $[3]; } let t3; - if ($[4] !== t1 || $[5] !== t0) { + if ($[4] !== t0 || $[5] !== t1) { t3 = (
{t2} @@ -68,8 +68,8 @@ function Component() { {t0}
); - $[4] = t1; - $[5] = t0; + $[4] = t0; + $[5] = t1; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md index 887479c01e..2fc302c8b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md @@ -43,11 +43,11 @@ function foo(a, b, c) { } const y = t1; let t2; - if ($[4] !== x || $[5] !== y.method || $[6] !== b) { + if ($[4] !== b || $[5] !== x || $[6] !== y.method) { t2 = x[y.method](b); - $[4] = x; - $[5] = y.method; - $[6] = b; + $[4] = b; + $[5] = x; + $[6] = y.method; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md index c32777534b..58c06101d3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md @@ -33,11 +33,11 @@ function foo(a, b, c) { const method = x.method; let t1; - if ($[2] !== method || $[3] !== x || $[4] !== b) { + if ($[2] !== b || $[3] !== method || $[4] !== x) { t1 = method.call(x, b); - $[2] = method; - $[3] = x; - $[4] = b; + $[2] = b; + $[3] = method; + $[4] = x; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md index ad45287d1f..fd8a4935a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md @@ -40,10 +40,10 @@ function foo(a, b, c) { } const x = t0; let t1; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { t1 = x.foo(b); - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md index 090e9d889c..a3403e6c8d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md @@ -73,8 +73,8 @@ import { identity, mutate } from "shared-runtime"; function useFoo(t0) { const $ = _c(4); const { a, b } = t0; - let z; let y; + let z; if ($[0] !== a || $[1] !== b) { const x = { a }; y = {}; @@ -83,11 +83,11 @@ function useFoo(t0) { mutate(y); $[0] = a; $[1] = b; - $[2] = z; - $[3] = y; + $[2] = y; + $[3] = z; } else { - z = $[2]; - y = $[3]; + y = $[2]; + z = $[3]; } if (z[0] !== y) { throw new Error("oh no!"); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md index 4b500f52d6..dc1cd699d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md @@ -40,7 +40,7 @@ function useHook(t0) { const { value } = t0; const [state] = useState(false); let t1; - if ($[0] !== value || $[1] !== state) { + if ($[0] !== state || $[1] !== value) { t1 = { getX() { return { @@ -52,8 +52,8 @@ function useHook(t0) { }; }, }; - $[0] = value; - $[1] = state; + $[0] = state; + $[1] = value; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md index 77875f789d..3dd8e73032 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md index 46767056bd..3cd9877813 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md index 6e44a97b45..60a6171ab1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md index 77ded20d93..2674d78c99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md index 10c23085d8..1d4a50d285 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..42b3b21a20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); - let y; let t0; + let y; if ($[0] !== props) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -57,11 +57,11 @@ function Component(props) { } } $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md index 9ca63e23c4..3da133e929 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(7); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; if ($[4] !== minWidth || $[5] !== width) { @@ -55,9 +55,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md index 947e3bd2eb..ee4e4634cb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md @@ -42,9 +42,9 @@ import { Stringify } from "shared-runtime"; function Foo(t0) { const $ = _c(8); const { arr1, arr2, foo } = t0; - let t1; let getVal1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + let t1; + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -55,13 +55,13 @@ function Foo(t0) { t1 = () => [y]; foo ? (y = x.concat(arr2)) : y; $[0] = arr1; - $[1] = foo; - $[2] = arr2; - $[3] = t1; - $[4] = getVal1; + $[1] = arr2; + $[2] = foo; + $[3] = getVal1; + $[4] = t1; } else { - t1 = $[3]; - getVal1 = $[4]; + getVal1 = $[3]; + t1 = $[4]; } const getVal2 = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md index 3a7016f803..f7353ddd5e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md @@ -44,10 +44,10 @@ function Component(t0) { t3 = $[1]; } let t4; - if ($[2] !== t3 || $[3] !== propA) { + if ($[2] !== propA || $[3] !== t3) { t4 = { value: t3, other: propA }; - $[2] = t3; - $[3] = propA; + $[2] = propA; + $[3] = t3; $[4] = t4; } else { t4 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md index f0ce9b9114..c6ad9bcdac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md @@ -34,10 +34,10 @@ function Component(t0) { const t2 = propB?.x.y; let t3; - if ($[0] !== t2 || $[1] !== propA) { + if ($[0] !== propA || $[1] !== t2) { t3 = { value: t2, other: propA }; - $[0] = t2; - $[1] = propA; + $[0] = propA; + $[1] = t2; $[2] = t3; } else { t3 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md index 9d7feacd6d..7a27bb8521 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(6); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; @@ -58,9 +58,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md index e9d2bffb30..5fc0ec510b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md @@ -44,7 +44,7 @@ function Foo(t0) { const { arr1, arr2, foo } = t0; let t1; let val1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -63,8 +63,8 @@ function Foo(t0) { foo ? (y = x.concat(arr2)) : y; t1 = (() => [y])(); $[0] = arr1; - $[1] = foo; - $[2] = arr2; + $[1] = arr2; + $[2] = foo; $[3] = t1; $[4] = val1; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md index e1d4bb5a8a..580a97ac19 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md @@ -49,7 +49,7 @@ import { identity, mutate, setProperty } from "shared-runtime"; function PrimitiveAsDepNested(props) { const $ = _c(5); let t0; - if ($[0] !== props.b || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.b) { const x = {}; mutate(x); const t1 = props.b + 1; @@ -64,8 +64,8 @@ function PrimitiveAsDepNested(props) { const y = t2; setProperty(x, props.a); t0 = [x, y]; - $[0] = props.b; - $[1] = props.a; + $[0] = props.a; + $[1] = props.b; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md index 594e68f24f..de39fc9706 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md @@ -32,15 +32,15 @@ function Component(t0) { const $ = _c(5); const { base, start, increment, test } = t0; let value; - if ($[0] !== base || $[1] !== start || $[2] !== test || $[3] !== increment) { + if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) { value = base; for (let i = start; i < test; i = i + increment, i) { value = value + i; } $[0] = base; - $[1] = start; - $[2] = test; - $[3] = increment; + $[1] = increment; + $[2] = start; + $[3] = test; $[4] = value; } else { value = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md index 476e1c017c..c235681d38 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-nested-early-return-within-reactive-scope.expect.md @@ -34,7 +34,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(7); let t0; - if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -69,9 +69,9 @@ function Component(props) { break bb0; } } - $[0] = props.cond; - $[1] = props.a; - $[2] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md index bf54b52b59..0e93d32061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/early-return-within-reactive-scope.expect.md @@ -48,7 +48,7 @@ import { makeArray } from "shared-runtime"; function Component(props) { const $ = _c(6); let t0; - if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -69,9 +69,9 @@ function Component(props) { break bb0; } } - $[0] = props.cond; - $[1] = props.a; - $[2] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md index 744824d0bd..2a3da8b195 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/iife-return-modified-later-phi.expect.md @@ -29,7 +29,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(3); let items; - if ($[0] !== props.cond || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.cond) { let t0; if (props.cond) { t0 = []; @@ -39,8 +39,8 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props.cond; - $[1] = props.a; + $[0] = props.a; + $[1] = props.cond; $[2] = items; } else { items = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md index d0486cd8c2..28612f2d73 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md index b4a55fcb61..2861ab71c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md index f15b9b8e9b..b5db44aa2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md index 324eb714fc..1972e92237 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md @@ -32,9 +32,9 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(6); - let y; let t0; - if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { + let y; + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,14 +57,14 @@ function Component(props) { } } } - $[0] = props.cond; - $[1] = props.a; - $[2] = props.b; - $[3] = y; - $[4] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md index 1bfaeb1c84..6c7a91ba33 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/phi-type-inference-property-store.expect.md @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props.cond || $[2] !== props.a) { + if ($[1] !== props.a || $[2] !== props.cond) { let y; if (props.cond) { y = {}; @@ -53,8 +53,8 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props.cond; - $[2] = props.a; + $[1] = props.a; + $[2] = props.cond; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md index c0f8aa97cd..673dd0d5fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md @@ -58,7 +58,7 @@ function useFoo(t0) { local = $[1]; } let t1; - if ($[2] !== shouldReadA || $[3] !== local) { + if ($[2] !== local || $[3] !== shouldReadA) { t1 = ( { @@ -70,8 +70,8 @@ function useFoo(t0) { shouldInvokeFns={true} /> ); - $[2] = shouldReadA; - $[3] = local; + $[2] = local; + $[3] = shouldReadA; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md index e37b8365a2..abf4c98f23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md @@ -41,7 +41,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a) { + if ($[0] !== a || $[1] !== shouldReadA) { t1 = ( { @@ -53,8 +53,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a; + $[0] = a; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md index 89b4d281f8..d82956e4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md @@ -51,13 +51,13 @@ function Foo(t0) { const fn = t1; useIdentity(null); let x; - if ($[2] !== cond || $[3] !== a.b.c) { + if ($[2] !== a.b.c || $[3] !== cond) { x = makeArray(); if (cond) { x.push(identity(a.b.c)); } - $[2] = cond; - $[3] = a.b.c; + $[2] = a.b.c; + $[3] = cond; $[4] = x; } else { x = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md index 591e04de7b..c81e59ecea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md @@ -50,22 +50,22 @@ function Foo(t0) { const fn = t1; useIdentity(null); let arr; - if ($[2] !== cond || $[3] !== a.b?.c.e) { + if ($[2] !== a.b?.c.e || $[3] !== cond) { arr = makeArray(); if (cond) { arr.push(identity(a.b?.c.e)); } - $[2] = cond; - $[3] = a.b?.c.e; + $[2] = a.b?.c.e; + $[3] = cond; $[4] = arr; } else { arr = $[4]; } let t2; - if ($[5] !== fn || $[6] !== arr) { + if ($[5] !== arr || $[6] !== fn) { t2 = ; - $[5] = fn; - $[6] = arr; + $[5] = arr; + $[6] = fn; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/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 index 2c7bf6bbe5..96d37cee8a 100644 --- 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 @@ -41,7 +41,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a) { + if ($[0] !== a || $[1] !== shouldReadA) { t1 = ( ); - $[0] = shouldReadA; - $[1] = a; + $[0] = a; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md index df9dec4fb6..277c203525 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(5); let x; - if ($[0] !== props.items?.length || $[1] !== props.items?.edges) { + if ($[0] !== props.items?.edges || $[1] !== props.items?.length) { x = []; x.push(props.items?.length); let t0; @@ -36,8 +36,8 @@ function Component(props) { t0 = $[4]; } x.push(t0); - $[0] = props.items?.length; - $[1] = props.items?.edges; + $[0] = props.items?.edges; + $[1] = props.items?.length; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md index 902a1578c8..30cea1e2c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md @@ -38,15 +38,15 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(4); let x; - if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { + if ($[0] !== other || $[1] !== props.a.a.a || $[2] !== props.a.b) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a.a.a; - $[1] = props.a.b; - $[2] = other; + $[0] = other; + $[1] = props.a.a.a; + $[2] = props.a.b; $[3] = x; } else { x = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md index c5cf366fb6..923830fb4b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/ssa-renaming-unconditional-ternary.expect.md @@ -39,11 +39,11 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { + if ($[2] !== props.bar || $[3] !== props.cond || $[4] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props.cond; - $[3] = props.foo; - $[4] = props.bar; + $[2] = props.bar; + $[3] = props.cond; + $[4] = props.foo; $[5] = x; } else { x = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md index 37846215b1..fee31c9faf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md @@ -35,8 +35,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { @@ -65,19 +65,19 @@ function Component(props) { t0 = ; $[0] = props.p0; $[1] = props.p2; - $[2] = y; - $[3] = t0; + $[2] = t0; + $[3] = y; } else { - y = $[2]; - t0 = $[3]; + t0 = $[2]; + y = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md index 1be4143849..6290668015 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md @@ -30,8 +30,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { @@ -48,19 +48,19 @@ function Component(props) { $[0] = props.p0; $[1] = props.p2; $[2] = props.p3; - $[3] = y; - $[4] = t0; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md index f69994b0a8..5cd81990e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch-escaping.expect.md @@ -34,7 +34,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let x; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { try { const y = []; y.push(props.y); @@ -44,8 +44,8 @@ function Component(props) { e.push(props.e); x = e; } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md index bc47228371..dc41af6275 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/try-catch-try-value-modified-in-catch.expect.md @@ -33,7 +33,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { try { @@ -47,8 +47,8 @@ function Component(props) { break bb0; } } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md index d654221dc8..a75b592b83 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md @@ -39,10 +39,10 @@ function Component(props) { bb0: { let y; if ( - $[0] !== props.cond || - $[1] !== props.a || - $[2] !== props.cond2 || - $[3] !== props.b + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.cond || + $[3] !== props.cond2 ) { y = []; if (props.cond) { @@ -54,10 +54,10 @@ function Component(props) { } y.push(props.b); - $[0] = props.cond; - $[1] = props.a; - $[2] = props.cond2; - $[3] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = props.cond2; $[4] = y; $[5] = t0; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md index 7480362a43..c6331bd4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md @@ -54,10 +54,10 @@ function Component(props) { } const c = t0; let t1; - if ($[3] !== c || $[4] !== a) { + if ($[3] !== a || $[4] !== c) { t1 = [c, a]; - $[3] = c; - $[4] = a; + $[3] = a; + $[4] = c; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md index 29e3e2757f..5ac29886cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md @@ -20,11 +20,11 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(8); let items; - if ($[0] !== props.key || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.key) { items = bar(); mutate(items[props.key], props.a); - $[0] = props.key; - $[1] = props.a; + $[0] = props.a; + $[1] = props.key; $[2] = items; } else { items = $[2]; @@ -41,10 +41,10 @@ function Component(props) { } const count = t1; let t2; - if ($[5] !== items || $[6] !== count) { + if ($[5] !== count || $[6] !== items) { t2 = { items, count }; - $[5] = items; - $[6] = count; + $[5] = count; + $[6] = items; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md index 3408f707d6..bb76df4de0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md @@ -40,10 +40,10 @@ function Component(props) { } const count = t1; let t2; - if ($[4] !== items || $[5] !== count) { + if ($[4] !== count || $[5] !== items) { t2 = { items, count }; - $[4] = items; - $[5] = count; + $[4] = count; + $[5] = items; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md index e682a01086..422f4cf547 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md @@ -42,8 +42,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(10); - let x; let t0; + let x; if ($[0] !== a) { x = []; if (a) { @@ -52,11 +52,11 @@ function foo(a, b, c) { t0 =
{x}
; $[0] = a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const y = t0; bb0: switch (b) { @@ -83,15 +83,15 @@ function foo(a, b, c) { } } let t1; - if ($[7] !== y || $[8] !== x) { + if ($[7] !== x || $[8] !== y) { t1 = (
{y} {x}
); - $[7] = y; - $[8] = x; + $[7] = x; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md index 3ad544344d..f3ddf57ec0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md @@ -34,7 +34,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md index 449aa6d3d8..5700634449 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md @@ -31,9 +31,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md index 4d45d3f3c6..18e9faf63b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== a.b.c || $[1] !== shouldReadA) { t1 = ( { @@ -50,8 +50,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a.b.c; + $[0] = a.b.c; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md index 701702f9dd..2dd9362404 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md @@ -80,10 +80,10 @@ function useFoo(t0) { x = $[6]; } let t1; - if ($[7] !== y || $[8] !== x.a.b) { + if ($[7] !== x.a.b || $[8] !== y) { t1 = [y, x.a.b]; - $[7] = y; - $[8] = x.a.b; + $[7] = x.a.b; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md index b4c25d5f45..e024bc893a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md index 359ba0dcde..055d1ce12e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; for (let i = 0; i < 5; i++) { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md index bb47587687..1c2162352b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md @@ -42,8 +42,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -77,11 +77,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md index 59225b5155..ca8228e2db 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md index 7a7f9a4b6e..ce12fb17b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md @@ -33,9 +33,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md index 2b925c2479..058bf85b69 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md @@ -39,8 +39,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { input, cond } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -61,11 +61,11 @@ function useFoo(t0) { } $[0] = cond; $[1] = input; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md index 291998c8ad..2715a0c92d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md @@ -40,7 +40,7 @@ function useFoo(t0) { const $ = _c(3); const { input, max } = t0; let x; - if ($[0] !== max || $[1] !== input.a.b) { + if ($[0] !== input.a.b || $[1] !== max) { x = []; let i = 0; while (true) { @@ -52,8 +52,8 @@ function useFoo(t0) { x.push(i); x.push(input.a.b); - $[0] = max; - $[1] = input.a.b; + $[0] = input.a.b; + $[1] = max; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md index 84b8fa1d43..845ea4b5ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md @@ -33,8 +33,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, hasAB, returnNull } = t0; - let x; let t1; + let x; if ($[0] !== hasAB || $[1] !== input.a || $[2] !== returnNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -68,11 +68,11 @@ function useFoo(t0) { $[0] = hasAB; $[1] = input.a; $[2] = returnNull; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md index a55922f610..d3e463495b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond2, cond1 } = t0; - let x; let t1; + let x; if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== input.a.b) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond1; $[1] = cond2; $[2] = input.a.b; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md index 8d69c008c5..7818ca4e0d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/memberexpr-join-optional-chain2.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(5); let x; - if ($[0] !== props.items?.length || $[1] !== props.items?.edges) { + if ($[0] !== props.items?.edges || $[1] !== props.items?.length) { x = []; x.push(props.items?.length); let t0; @@ -35,8 +35,8 @@ function Component(props) { t0 = $[4]; } x.push(t0); - $[0] = props.items?.length; - $[1] = props.items?.edges; + $[0] = props.items?.edges; + $[1] = props.items?.length; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..d3a61a1019 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -36,14 +36,14 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== other || $[1] !== props.a) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; + $[0] = other; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md index 01ab4f6b0a..41a7a25d63 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md @@ -34,9 +34,9 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, inputHasAB, inputHasABC } = t0; - let x; let t1; - if ($[0] !== inputHasABC || $[1] !== input.a || $[2] !== inputHasAB) { + let x; + if ($[0] !== input.a || $[1] !== inputHasAB || $[2] !== inputHasABC) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -75,14 +75,14 @@ function useFoo(t0) { x.push(t2); } } - $[0] = inputHasABC; - $[1] = input.a; - $[2] = inputHasAB; - $[3] = x; - $[4] = t1; + $[0] = input.a; + $[1] = inputHasAB; + $[2] = inputHasABC; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md index a62223d72d..03e3e96397 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md @@ -40,14 +40,14 @@ import { identity } from "shared-runtime"; function useConditionalSubpath1(props, cond) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.b = props.a.b; if (identity(cond)) { x.a = props.a; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md index 02117feee2..5b246175f9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md @@ -48,14 +48,14 @@ function useConditionalSuperpath1(t0) { const $ = _c(3); const { props, cond } = t0; let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.a = props.a; if (identity(cond)) { x.b = props.a.b; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md index 34979e9de9..c91cf94445 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md @@ -49,15 +49,15 @@ function Component(t0) { const $ = _c(8); const { cond, other } = t0; let x; - if ($[0] !== other || $[1] !== cond) { + if ($[0] !== cond || $[1] !== other) { x = makeObject_Primitives(); setProperty(x, { b: 3, other }, "a"); identity(x.a.b); if (!cond) { x.a = null; } - $[0] = other; - $[1] = cond; + $[0] = cond; + $[1] = other; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md index 0a1a423361..ff15f1a336 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-nonoverlap-descendant.expect.md @@ -27,13 +27,13 @@ import { c as _c } from "react/compiler-runtime"; // Test that we can track non- function TestNonOverlappingDescendantTracked(props) { const $ = _c(4); let x; - if ($[0] !== props.a.x.y || $[1] !== props.a.c.x.y.z || $[2] !== props.b) { + if ($[0] !== props.a.c.x.y.z || $[1] !== props.a.x.y || $[2] !== props.b) { x = {}; x.a = props.a.x.y; x.b = props.b; x.c = props.a.c.x.y.z; - $[0] = props.a.x.y; - $[1] = props.a.c.x.y.z; + $[0] = props.a.c.x.y.z; + $[1] = props.a.x.y; $[2] = props.b; $[3] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md index da6912d35e..a2bd99e9a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md @@ -82,10 +82,10 @@ function Component(t0) { } const b = t3; let t4; - if ($[4] !== b || $[5] !== a) { + if ($[4] !== a || $[5] !== b) { t4 = { b, a }; - $[4] = b; - $[5] = a; + $[4] = a; + $[5] = b; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md index bba0b3f139..0089d20af2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md @@ -59,7 +59,7 @@ function Component(t0) { const serverTime = useServerTime(); let t1; let timestampLabel; - if ($[0] !== highlightedItem || $[1] !== serverTime || $[2] !== label) { + if ($[0] !== highlightedItem || $[1] !== label || $[2] !== serverTime) { const highlight = new Highlight(highlightedItem); const time = serverTime.get(); @@ -68,8 +68,8 @@ function Component(t0) { t1 = highlight.render(); $[0] = highlightedItem; - $[1] = serverTime; - $[2] = label; + $[1] = label; + $[2] = serverTime; $[3] = t1; $[4] = timestampLabel; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md index 9c21dc8400..ecdfa88b98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md @@ -66,10 +66,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md index 1b6e91a6fd..f9d725e0b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md @@ -65,10 +65,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md index 2dabc256f9..1ad6f6a047 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md @@ -71,10 +71,10 @@ function Foo() { const shouldCaptureObj = obj != null && CONST_TRUE; const t0 = shouldCaptureObj ? identity(obj) : null; let t1; - if ($[0] !== t0 || $[1] !== obj) { + if ($[0] !== obj || $[1] !== t0) { t1 = [t0, obj]; - $[0] = t0; - $[1] = obj; + $[0] = obj; + $[1] = t0; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md index 4921fd340b..d35cbe266f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md @@ -77,15 +77,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md index 80d046ec18..eda7a0cbfe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md @@ -74,15 +74,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md index 5eff1aa0fe..b5a7827728 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md @@ -21,13 +21,13 @@ function Component(listItem, thread) { let t0; let t1; let t2; - if ($[0] !== thread.threadType || $[1] !== listItem) { + if ($[0] !== listItem || $[1] !== thread.threadType) { const isFoo = isFooThread(thread.threadType); t1 = useBar; t2 = listItem; t0 = getBadgeText(listItem, isFoo); - $[0] = thread.threadType; - $[1] = listItem; + $[0] = listItem; + $[1] = thread.threadType; $[2] = t0; $[3] = t1; $[4] = t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md index 1d4a4b5d67..32cbbb2b91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md @@ -28,7 +28,7 @@ function V0(t0) { const { v1, v2 } = t0; const v5 = v1.v6?.v7; let t1; - if ($[0] !== v5 || $[1] !== v1 || $[2] !== v2) { + if ($[0] !== v1 || $[1] !== v2 || $[2] !== v5) { t1 = ( {v5 != null ? ( @@ -40,9 +40,9 @@ function V0(t0) { )} ); - $[0] = v5; - $[1] = v1; - $[2] = v2; + $[0] = v1; + $[1] = v2; + $[2] = v5; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md index cec64e8cf0..ab409b366d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md @@ -36,7 +36,7 @@ function useTest(t0) { const $ = _c(3); const { isNull, data } = t0; let t1; - if ($[0] !== isNull || $[1] !== data) { + if ($[0] !== data || $[1] !== isNull) { t1 = Builder.makeBuilder(isNull, "hello world") ?.push("1", 2) ?.push(3, { a: 4, b: 5, c: data }) @@ -47,8 +47,8 @@ function useTest(t0) { ) ?.push(7, "8") ?.push("8", Builder.makeBuilder(!isNull)?.push(9).vals)?.vals; - $[0] = isNull; - $[1] = data; + $[0] = data; + $[1] = isNull; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md index 31180b2236..fe7857a355 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unmerged-fbt-call-merge-overlapping-reactive-scopes.expect.md @@ -38,7 +38,7 @@ import { Stringify } from "shared-runtime"; function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.value.length || $[1] !== props.cond) { + if ($[0] !== props.cond || $[1] !== props.value.length) { const label = fbt._( { "*": "{number} bars", _1: "1 bar" }, [fbt._plural(props.value.length, "number")], @@ -51,8 +51,8 @@ function Component(props) { label={label.toString()} /> ) : null; - $[0] = props.value.length; - $[1] = props.cond; + $[0] = props.cond; + $[1] = props.value.length; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md index 73674e5fff..496d80d52b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md @@ -77,10 +77,10 @@ function Component(t0) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== result) { + if ($[4] !== result || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = result; + $[4] = result; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md index 2e9daceed7..1b49552bea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md @@ -26,8 +26,8 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); const item = props.item; - let t0; let baseVideos; + let t0; let thumbnails; if ($[0] !== item) { thumbnails = []; @@ -40,12 +40,12 @@ function Component(props) { } }); $[0] = item; - $[1] = t0; - $[2] = baseVideos; + $[1] = baseVideos; + $[2] = t0; $[3] = thumbnails; } else { - t0 = $[1]; - baseVideos = $[2]; + baseVideos = $[1]; + t0 = $[2]; thumbnails = $[3]; } t0 = undefined; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md index 9aa75b2162..aece9665c9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const [bar] = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md index ded1eb4006..3681e9c469 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const bar = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md index f0a46be168..3e66b20041 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const { bar } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md index 7c9c6a5028..1201a9a737 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md @@ -73,14 +73,14 @@ function foo(props) { } const header = t0; let y; - if ($[5] !== x || $[6] !== props.b || $[7] !== props.c) { + if ($[5] !== props.b || $[6] !== props.c || $[7] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[5] = x; - $[6] = props.b; - $[7] = props.c; + $[5] = props.b; + $[6] = props.c; + $[7] = x; $[8] = y; $[9] = x; } else { @@ -103,15 +103,15 @@ function foo(props) { } const content = t1; let t2; - if ($[13] !== header || $[14] !== content) { + if ($[13] !== content || $[14] !== header) { t2 = ( <> {header} {content} ); - $[13] = header; - $[14] = content; + $[13] = content; + $[14] = header; $[15] = t2; } else { t2 = $[15]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md index 36b1d4541a..143496678e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md @@ -53,30 +53,30 @@ import { c as _c } from "react/compiler-runtime"; // note: comments are for the // emitted function foo(props) { const $ = _c(14); - let x; let t0; + let x; if ($[0] !== props.a) { x = []; x.push(props.a); t0 =
{x}
; $[0] = props.a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const header = t0; let y; - if ($[3] !== x || $[4] !== props.b || $[5] !== props.c) { + if ($[3] !== props.b || $[4] !== props.c || $[5] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[3] = x; - $[4] = props.b; - $[5] = props.c; + $[3] = props.b; + $[4] = props.c; + $[5] = x; $[6] = y; $[7] = x; } else { @@ -99,15 +99,15 @@ function foo(props) { } const content = t1; let t2; - if ($[11] !== header || $[12] !== content) { + if ($[11] !== content || $[12] !== header) { t2 = ( <> {header} {content} ); - $[11] = header; - $[12] = content; + $[11] = content; + $[12] = header; $[13] = t2; } else { t2 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md index 5f10f44202..36a68d07c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md @@ -43,9 +43,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); - let text; let t0; let t1; + let text; if ($[0] !== statusName) { const { status, text: t2 } = foo(statusName); text = t2; @@ -54,13 +54,13 @@ function Component(statusName) { t1 = identity(bg); t0 = identity(color); $[0] = statusName; - $[1] = text; - $[2] = t0; - $[3] = t1; + $[1] = t0; + $[2] = t1; + $[3] = text; } else { - text = $[1]; - t0 = $[2]; - t1 = $[3]; + t0 = $[1]; + t1 = $[2]; + text = $[3]; } let t2; if ($[4] !== text) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md index e2cd53bd0d..d8e991dc46 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md @@ -46,9 +46,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); + let font; let t0; let text; - let font; if ($[0] !== statusName) { const { status, text: t1 } = foo(statusName); text = t1; @@ -58,13 +58,13 @@ function Component(statusName) { t0 = identity(color); $[0] = statusName; - $[1] = t0; - $[2] = text; - $[3] = font; + $[1] = font; + $[2] = t0; + $[3] = text; } else { - t0 = $[1]; - text = $[2]; - font = $[3]; + font = $[1]; + t0 = $[2]; + text = $[3]; } const bg = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..788109636b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -34,8 +34,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); - let y; let t0; + let y; if ($[0] !== props) { const x = []; bb0: switch (props.p0) { @@ -63,19 +63,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[4] !== child || $[5] !== y) { t1 = {child}; - $[4] = y; - $[5] = child; + $[4] = child; + $[5] = y; $[6] = t1; } else { t1 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..2628982655 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -29,8 +29,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(6); - let y; let t0; + let y; if ($[0] !== props) { const x = []; switch (props.p0) { @@ -45,19 +45,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[3] !== child || $[4] !== y) { t1 = {child}; - $[3] = y; - $[4] = child; + $[3] = child; + $[4] = y; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md index f106382d64..4864c51a1f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -64,8 +64,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -85,15 +85,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md index 77fd38aea1..c661094fdd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md @@ -55,7 +55,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -70,8 +70,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,15 +91,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md index d064a48b71..7ac6486b47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md @@ -18,10 +18,10 @@ function App() { const $ = _c(3); const [foo, bar] = useContext(MyContext); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md index f82af06866..3eac66304b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md @@ -22,10 +22,10 @@ function App() { const { foo } = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md index 573b6db231..4cca8b19d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md @@ -22,10 +22,10 @@ function App() { const [foo] = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md index 03ce7f97ba..f5a3916626 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md @@ -22,10 +22,10 @@ function App() { const { joe: t0, bar } = useContext(MyContext); const { foo } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md index 55387503cf..0888d67b2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md @@ -22,10 +22,10 @@ function App() { const foo = context.foo; const bar = context.bar; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md index 268fa8d7eb..2c27360c9f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md @@ -42,9 +42,9 @@ import { mutate, setProperty, throwErrorWithMessageIf } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { value, cond } = t0; - let y; let t1; - if ($[0] !== value || $[1] !== cond) { + let y; + if ($[0] !== cond || $[1] !== value) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { y = [value]; @@ -68,13 +68,13 @@ function useFoo(t0) { } y.push(x); } - $[0] = value; - $[1] = cond; - $[2] = y; - $[3] = t1; + $[0] = cond; + $[1] = value; + $[2] = t1; + $[3] = y; } else { - y = $[2]; - t1 = $[3]; + t1 = $[2]; + y = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md index 700df01c5c..5e57a46018 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md @@ -33,7 +33,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let x; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { try { const y = []; y.push(props.y); @@ -43,8 +43,8 @@ function Component(props) { e.push(props.e); x = e; } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md index c740c7d6b6..921d657e16 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md @@ -32,7 +32,7 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.y || $[1] !== props.e) { + if ($[0] !== props.e || $[1] !== props.y) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { try { @@ -46,8 +46,8 @@ function Component(props) { break bb0; } } - $[0] = props.y; - $[1] = props.e; + $[0] = props.e; + $[1] = props.y; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md index b04bd53458..562c0bc1c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md @@ -32,8 +32,8 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -47,11 +47,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md index af5f2ebfcf..71a59aba2f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md @@ -33,8 +33,8 @@ const { shallowCopy, throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -52,11 +52,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md index 54d5be2d6b..c3c45beb86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md @@ -78,10 +78,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -95,10 +95,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md index 072c6d03d9..4acbd2dfdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md @@ -76,10 +76,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -93,10 +93,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md index caa74267f3..5016b0c4df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md index a92abd4ca5..3f341fc665 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md index fbd13dc1b0..7fa86838e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md @@ -38,22 +38,22 @@ function component(a) { const f = typeof t.t; let t0; if ( - $[0] !== z || - $[1] !== p || - $[2] !== q || + $[0] !== e || + $[1] !== f || + $[2] !== m || $[3] !== n || - $[4] !== m || - $[5] !== e || - $[6] !== f + $[4] !== p || + $[5] !== q || + $[6] !== z ) { t0 = { z, p, q, n, m, e, f }; - $[0] = z; - $[1] = p; - $[2] = q; + $[0] = e; + $[1] = f; + $[2] = m; $[3] = n; - $[4] = m; - $[5] = e; - $[6] = f; + $[4] = p; + $[5] = q; + $[6] = z; $[7] = t0; } else { t0 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md index 663a6788f3..7f79cae4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md @@ -89,10 +89,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..d94a5e7e37 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -109,10 +109,10 @@ function Inner(props) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== output) { + if ($[4] !== output || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = output; + $[4] = output; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md index 99effce934..cf0093ea94 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md @@ -91,10 +91,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md index 1292ea1489..c3e115fa0d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md @@ -51,7 +51,7 @@ function Component(props) { } const exit = t0; let t1; - if ($[2] !== item.value || $[3] !== exit) { + if ($[2] !== exit || $[3] !== item.value) { t1 = () => { const cleanup = GlobalEventEmitter.addListener("onInput", () => { if (item.value) { @@ -60,8 +60,8 @@ function Component(props) { }); return () => cleanup.remove(); }; - $[2] = item.value; - $[3] = exit; + $[2] = exit; + $[3] = item.value; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md index b6a4187355..28d3e33359 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md @@ -62,13 +62,13 @@ function Component(props) { {w} ); - let condition = $[2] !== x || $[3] !== w; + let condition = $[2] !== w || $[3] !== x; if (!condition) { let old$t1 = $[4]; $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(7:10)"); } - $[2] = x; - $[3] = w; + $[2] = w; + $[3] = x; $[4] = t1; if (condition) { t1 = ( From 62b4891d09d65053b0364bcdec72df4d83aa8532 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:34:36 -0500 Subject: [PATCH 088/422] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200 Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From 1b214525b22b650eb4a5b349d3e927b5a15d9326 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:34:35 -0500 Subject: [PATCH 089/422] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR ' --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 11 +- .../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, 566 insertions(+), 1868 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 1189f2e125..1d2e155848 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,16 +231,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..68b0122ea9 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.a || $[1] !== props.b || $[2] !== props.cond) { 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.a; + $[1] = props.b; + $[2] = props.cond; + $[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..31df829e0c 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.a || $[1] !== props.b || $[2] !== props.cond) { 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.a; + $[1] = props.b; + $[2] = props.cond; + $[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 32498e1379..5614560c6c 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 4a62bf6f24..c7aa3e3b75 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..22f967883b 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.a || $[1] !== props.cond) { 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.a; + $[1] = props.cond; + $[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 2674d78c99..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] !== props.cond || $[4] !== t1) { - t2 = [t1, props.cond]; - $[3] = props.cond; - $[4] = t1; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== data || $[7] !== t2) { - t3 = ; - $[6] = data; - $[7] = t2; - $[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 1d4a50d285..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] !== props.cond || $[4] !== t1) { - t2 = [t1, props.cond]; - $[3] = props.cond; - $[4] = t1; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== data || $[7] !== t2) { - t3 = ; - $[6] = data; - $[7] = t2; - $[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 42b3b21a20..87c9485d99 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 t0; let y; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { 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] = t0; - $[2] = y; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = t0; + $[4] = y; } else { - t0 = $[1]; - y = $[2]; + t0 = $[3]; + y = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/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..9223c61200 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.a || $[2] !== props.cond) { 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.a; + $[2] = props.cond; + $[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 18e9faf63b..f68c826507 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] !== a.b.c || $[1] !== shouldReadA) { + if ($[0] !== a || $[1] !== shouldReadA) { t1 = ( { @@ -50,7 +50,7 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = a.b.c; + $[0] = a; $[1] = shouldReadA; $[2] = t1; } else { 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 d3a61a1019..83e050da29 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] !== other || $[1] !== props.a) { + if ($[0] !== other || $[1] !== props.a.a.a || $[2] !== props.a.b) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } $[0] = other; - $[1] = props.a; - $[2] = x; + $[1] = props.a.a.a; + $[2] = props.a.b; + $[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..58723f9fa0 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.bar || $[3] !== props.cond || $[4] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.bar; + $[3] = props.cond; + $[4] = props.foo; + $[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 788109636b..804acfce19 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 t0; let y; - 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] = t0; - $[2] = y; + $[0] = props.p0; + $[1] = props.p2; + $[2] = t0; + $[3] = y; } else { - t0 = $[1]; - y = $[2]; + t0 = $[2]; + y = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== child || $[5] !== y) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[4] = child; - $[5] = y; - $[6] = t1; + $[5] = child; + $[6] = y; + $[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 2628982655..33a7ae3193 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 t0; let y; - 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] = t0; - $[2] = y; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = t0; + $[4] = y; } else { - t0 = $[1]; - y = $[2]; + t0 = $[3]; + y = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== child || $[4] !== y) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[3] = child; - $[4] = y; - $[5] = t1; + $[5] = child; + $[6] = y; + $[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..23b05b5482 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.a || + $[1] !== props.b || + $[2] !== props.cond || + $[3] !== props.cond2 + ) { 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.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = props.cond2; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 12fe5566cdbe7720a554ebaa9338046dc24fd934 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:34:36 -0500 Subject: [PATCH 090/422] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...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 | 26 +++--- ...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 ++++--- 14 files changed, 191 insertions(+), 195 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 1d2e155848..855bc039ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,6 +231,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/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..9ce4a62e71 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]; + let t2; + if ($[1] !== a || $[2] !== x) { + t2 = () => [a, x]; $[1] = a; - $[2] = t2; - $[3] = t3; + $[2] = x; + $[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 fd0f32fbb3f7263041023674e3fc09d2dfffd33c Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:38:37 -0500 Subject: [PATCH 091/422] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 9494436d1f..ecc22365dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index b868afc52b..4eb53a17ae 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 d885efe520576783c45599ba32c4d0cd8041499b Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:38:37 -0500 Subject: [PATCH 092/422] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index 14532562fb..9a98f76b97 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] !== bar || $[1] !== foo) { 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] = bar; $[1] = foo; $[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 4eb53a17ae..cb5ebafadc 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 f83234f5fc1624ab7d7217f28729e234abffacff Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:38:37 -0500 Subject: [PATCH 093/422] [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 5d8d11f636829d5f737b1e6ddc36dabd3242f6cd Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:49:39 -0500 Subject: [PATCH 094/422] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200 Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++---- .../src/HIR/PropagateScopeDependenciesHIR.ts | 110 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 139 insertions(+), 54 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..2b7c9f2134 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -3,7 +3,6 @@ import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, BasicBlock, - InstructionId, IdentifierId, ReactiveScopeDependency, BranchTerminal, @@ -15,6 +14,8 @@ import { OptionalTerminal, HIRFunction, DependencyPathEntry, + Instruction, + Terminal, } from './HIR'; import {printIdentifier} from './PrintHIR'; @@ -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(), 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,10 @@ 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: 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 +107,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: Set; 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 @@ -137,7 +159,7 @@ function matchOptionalTestBlock( consequentId: IdentifierId; property: string; propertyId: IdentifierId; - storeLocalInstrId: InstructionId; + storeLocalInstr: Instruction; consequentGoto: BlockId; } | null { const consequentBlock = assertNonNull(blocks.get(terminal.consequent)); @@ -149,7 +171,7 @@ function matchOptionalTestBlock( const propertyLoad: TInstruction = consequentBlock .instructions[0] as TInstruction; const storeLocal: StoreLocal = consequentBlock.instructions[1].value; - const storeLocalInstrId = consequentBlock.instructions[1].id; + const storeLocalInstr = consequentBlock.instructions[1]; CompilerError.invariant( propertyLoad.value.object.identifier.id === terminal.test.identifier.id, { @@ -189,7 +211,7 @@ function matchOptionalTestBlock( consequentId: storeLocal.lvalue.place.identifier.id, property: propertyLoad.value.property, propertyId: propertyLoad.lvalue.identifier.id, - storeLocalInstrId, + storeLocalInstr, consequentGoto: consequentBlock.terminal.block, }; } @@ -369,10 +391,8 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, - ); - context.processedInstrsInOptional.add(test.id); + context.processedInstrsInOptional.add(matchConsequentResult.storeLocalInstr); + context.processedInstrsInOptional.add(test); 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..bbec25a57c 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,7 @@ import { DeclarationId, areEqualPaths, IdentifierId, + Terminal, } from './HIR'; import { collectHoistablePropertyLoads, @@ -176,8 +177,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 +218,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 +249,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 +359,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 +415,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 +641,7 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlySet, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -614,12 +680,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!processedInstrsInOptional.has(instr)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!processedInstrsInOptional.has(block.terminal)) { 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 e8a7ef6f9ba10c89b0e7fab43b908d8f96de040c Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:49:40 -0500 Subject: [PATCH 095/422] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...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 | 26 +++--- ...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 ++++--- 14 files changed, 191 insertions(+), 195 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 1d2e155848..855bc039ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,6 +231,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index bbec25a57c..35fad72054 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -661,35 +661,55 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); - 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 (!processedInstrsInOptional.has(instr)) { + handleInstruction(instr, context); + } + } + + if (!processedInstrsInOptional.has(block.terminal)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr)) { - handleInstruction(instr, context); - } - } + }; - if (!processedInstrsInOptional.has(block.terminal)) { - 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/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..9ce4a62e71 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]; + let t2; + if ($[1] !== a || $[2] !== x) { + t2 = () => [a, x]; $[1] = a; - $[2] = t2; - $[3] = t3; + $[2] = x; + $[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 63ceab6f8dcb2aac96dfd986d8108f510914fc78 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:49:40 -0500 Subject: [PATCH 096/422] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 9494436d1f..ecc22365dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index b868afc52b..4eb53a17ae 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 72b80516101292f8c969808ba53b2be2d8e49136 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:49:40 -0500 Subject: [PATCH 097/422] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index 14532562fb..9a98f76b97 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] !== bar || $[1] !== foo) { 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] = bar; $[1] = foo; $[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 4eb53a17ae..cb5ebafadc 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 a2b66f7a4b6a77700da673f067d54027fe3605f2 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 18:49:40 -0500 Subject: [PATCH 098/422] [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 723f8b985e925d381b5f452dd1ee8546a516b676 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 11 Nov 2024 11:29:28 -0500 Subject: [PATCH 099/422] [compiler] Repro for mutable range edge case See test fixtures --- ...g-aliased-capture-aliased-mutate.expect.md | 107 ++++++++++++++++++ .../bug-aliased-capture-aliased-mutate.js | 53 +++++++++ .../bug-aliased-capture-mutate.expect.md | 87 ++++++++++++++ .../compiler/bug-aliased-capture-mutate.js | 34 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 + 5 files changed, 283 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md new file mode 100644 index 0000000000..d0ad9e2f9d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md @@ -0,0 +1,107 @@ + +## Input + +```javascript +// @flow @enableTransitivelyFreezeFunctionExpressions:false +import {arrayPush, setPropertyByKey, Stringify} from 'shared-runtime'; + +/** + * 1. `InferMutableRanges` derives the mutable range of identifiers and their + * aliases from `LoadLocal`, `PropertyLoad`, etc + * - After this pass, y's mutable range only extends to `arrayPush(x, y)` + * - We avoid assigning mutable ranges to loads after y's mutable range, as + * these are working with an immutable value. As a result, `LoadLocal y` and + * `PropertyLoad y` do not get mutable ranges + * 2. `InferReactiveScopeVariables` extends mutable ranges and creates scopes, + * as according to the 'co-mutation' of different values + * - Here, we infer that + * - `arrayPush(y, x)` might alias `x` and `y` to each other + * - `setPropertyKey(x, ...)` may mutate both `x` and `y` + * - This pass correctly extends the mutable range of `y` + * - Since we didn't run `InferMutableRange` logic again, the LoadLocal / + * PropertyLoads still don't have a mutable range + * + * Note that the this bug is an edge case. Compiler output is only invalid for: + * - function expressions with + * `enableTransitivelyFreezeFunctionExpressions:false` + * - functions that throw and get retried without clearing the memocache + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":11},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ */ +function useFoo({a, b}: {a: number, b: number}) { + const x = []; + const y = {value: a}; + + arrayPush(x, y); // x and y co-mutate + const y_alias = y; + const cb = () => y_alias.value; + setPropertyByKey(x[0], 'value', b); // might overwrite y.value + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2, b: 10}], + sequentialRenders: [ + {a: 2, b: 10}, + {a: 2, b: 11}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { arrayPush, setPropertyByKey, Stringify } from "shared-runtime"; + +function useFoo(t0) { + const $ = _c(5); + const { a, b } = t0; + let t1; + if ($[0] !== a || $[1] !== b) { + const x = []; + const y = { value: a }; + + arrayPush(x, y); + const y_alias = y; + let t2; + if ($[3] !== y_alias.value) { + t2 = () => y_alias.value; + $[3] = y_alias.value; + $[4] = t2; + } else { + t2 = $[4]; + } + const cb = t2; + setPropertyByKey(x[0], "value", b); + t1 = ; + $[0] = a; + $[1] = b; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: 2, b: 10 }], + sequentialRenders: [ + { a: 2, b: 10 }, + { a: 2, b: 11 }, + ], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.js new file mode 100644 index 0000000000..c46ecd6250 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.js @@ -0,0 +1,53 @@ +// @flow @enableTransitivelyFreezeFunctionExpressions:false +import {arrayPush, setPropertyByKey, Stringify} from 'shared-runtime'; + +/** + * 1. `InferMutableRanges` derives the mutable range of identifiers and their + * aliases from `LoadLocal`, `PropertyLoad`, etc + * - After this pass, y's mutable range only extends to `arrayPush(x, y)` + * - We avoid assigning mutable ranges to loads after y's mutable range, as + * these are working with an immutable value. As a result, `LoadLocal y` and + * `PropertyLoad y` do not get mutable ranges + * 2. `InferReactiveScopeVariables` extends mutable ranges and creates scopes, + * as according to the 'co-mutation' of different values + * - Here, we infer that + * - `arrayPush(y, x)` might alias `x` and `y` to each other + * - `setPropertyKey(x, ...)` may mutate both `x` and `y` + * - This pass correctly extends the mutable range of `y` + * - Since we didn't run `InferMutableRange` logic again, the LoadLocal / + * PropertyLoads still don't have a mutable range + * + * Note that the this bug is an edge case. Compiler output is only invalid for: + * - function expressions with + * `enableTransitivelyFreezeFunctionExpressions:false` + * - functions that throw and get retried without clearing the memocache + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":11},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ */ +function useFoo({a, b}: {a: number, b: number}) { + const x = []; + const y = {value: a}; + + arrayPush(x, y); // x and y co-mutate + const y_alias = y; + const cb = () => y_alias.value; + setPropertyByKey(x[0], 'value', b); // might overwrite y.value + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2, b: 10}], + sequentialRenders: [ + {a: 2, b: 10}, + {a: 2, b: 11}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md new file mode 100644 index 0000000000..c35efe6a16 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md @@ -0,0 +1,87 @@ + +## Input + +```javascript +// @flow @enableTransitivelyFreezeFunctionExpressions:false +import {setPropertyByKey, Stringify} from 'shared-runtime'; + +/** + * Variation of bug in `bug-aliased-capture-aliased-mutate` + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":3},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ */ + +function useFoo({a}: {a: number, b: number}) { + const arr = []; + const obj = {value: a}; + + setPropertyByKey(obj, 'arr', arr); + const obj_alias = obj; + const cb = () => obj_alias.arr.length; + for (let i = 0; i < a; i++) { + arr.push(i); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2}], + sequentialRenders: [{a: 2}, {a: 3}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { setPropertyByKey, Stringify } from "shared-runtime"; + +function useFoo(t0) { + const $ = _c(4); + const { a } = t0; + let t1; + if ($[0] !== a) { + const arr = []; + const obj = { value: a }; + + setPropertyByKey(obj, "arr", arr); + const obj_alias = obj; + let t2; + if ($[2] !== obj_alias.arr.length) { + t2 = () => obj_alias.arr.length; + $[2] = obj_alias.arr.length; + $[3] = t2; + } else { + t2 = $[3]; + } + const cb = t2; + for (let i = 0; i < a; i++) { + arr.push(i); + } + + t1 = ; + $[0] = a; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: 2 }], + sequentialRenders: [{ a: 2 }, { a: 3 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.js new file mode 100644 index 0000000000..a7e5767266 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.js @@ -0,0 +1,34 @@ +// @flow @enableTransitivelyFreezeFunctionExpressions:false +import {setPropertyByKey, Stringify} from 'shared-runtime'; + +/** + * Variation of bug in `bug-aliased-capture-aliased-mutate` + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":3},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ */ + +function useFoo({a}: {a: number, b: number}) { + const arr = []; + const obj = {value: a}; + + setPropertyByKey(obj, 'arr', arr); + const obj_alias = obj; + const cb = () => obj_alias.arr.length; + for (let i = 0; i < a; i++) { + arr.push(i); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2}], + sequentialRenders: [{a: 2}, {a: 3}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index b868afc52b..1971fe0d33 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -480,6 +480,8 @@ const skipFilter = new Set([ 'fbt/bug-fbt-plural-multiple-mixed-call-tag', 'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr', 'bug-invalid-hoisting-functionexpr', + 'bug-aliased-capture-aliased-mutate', + 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', From 5388985a55eb5ae043d147298b20b0aa1c443b8d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 12 Nov 2024 12:45:58 -0500 Subject: [PATCH 100/422] [compiler] repro for reactive ref.current accesses Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: --- .../compiler/bug-nonreactive-ref.expect.md | 89 +++++++++++++++++++ .../fixtures/compiler/bug-nonreactive-ref.tsx | 33 +++++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 123 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md new file mode 100644 index 0000000000..f79df15d52 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md @@ -0,0 +1,89 @@ + +## Input + +```javascript +import {useRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Bug: we're currently filtering out `ref.current` dependencies in + * `propagateScopeDependencies:checkValidDependency`. This is incorrect. + * Instead, we should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ */ +function Component({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + const cb = () => ref.current; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useRef } from "react"; +import { Stringify } from "shared-runtime"; + +/** + * Bug: we're currently filtering out `ref.current` dependencies in + * `propagateScopeDependencies:checkValidDependency`. This is incorrect. + * Instead, we should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ */ +function Component(t0) { + const $ = _c(1); + const { cond } = t0; + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + let t1; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + const cb = () => ref.current; + t1 = ; + $[0] = t1; + } else { + t1 = $[0]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], + sequentialRenders: [{ cond: true }, { cond: false }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx new file mode 100644 index 0000000000..a0115f2df3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx @@ -0,0 +1,33 @@ +import {useRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Bug: we're currently filtering out `ref.current` dependencies in + * `propagateScopeDependencies:checkValidDependency`. This is incorrect. + * Instead, we should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ */ +function Component({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + const cb = () => ref.current; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 1971fe0d33..6c136f6310 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -484,6 +484,7 @@ const skipFilter = new Set([ 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', + 'bug-nonreactive-ref', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond', From 1da70f48990f64579ecd86ee86044ff0ceb948ab Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 12 Nov 2024 12:45:58 -0500 Subject: [PATCH 101/422] [compiler] repro for reactive ref.current accesses See test fixture --- .../compiler/bug-nonreactive-ref.expect.md | 89 +++++++++++++++++++ .../fixtures/compiler/bug-nonreactive-ref.tsx | 33 +++++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 123 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md new file mode 100644 index 0000000000..f79df15d52 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md @@ -0,0 +1,89 @@ + +## Input + +```javascript +import {useRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Bug: we're currently filtering out `ref.current` dependencies in + * `propagateScopeDependencies:checkValidDependency`. This is incorrect. + * Instead, we should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ */ +function Component({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + const cb = () => ref.current; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useRef } from "react"; +import { Stringify } from "shared-runtime"; + +/** + * Bug: we're currently filtering out `ref.current` dependencies in + * `propagateScopeDependencies:checkValidDependency`. This is incorrect. + * Instead, we should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ */ +function Component(t0) { + const $ = _c(1); + const { cond } = t0; + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + let t1; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + const cb = () => ref.current; + t1 = ; + $[0] = t1; + } else { + t1 = $[0]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], + sequentialRenders: [{ cond: true }, { cond: false }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx new file mode 100644 index 0000000000..a0115f2df3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx @@ -0,0 +1,33 @@ +import {useRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Bug: we're currently filtering out `ref.current` dependencies in + * `propagateScopeDependencies:checkValidDependency`. This is incorrect. + * Instead, we should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+ */ +function Component({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + const cb = () => ref.current; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 1971fe0d33..6c136f6310 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -484,6 +484,7 @@ const skipFilter = new Set([ 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', + 'bug-nonreactive-ref', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond', From c4459654f7168f1311790a1a65fd9876da9198bf Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 12 Nov 2024 13:30:43 -0500 Subject: [PATCH 102/422] [compiler] Fix: ref.current now correctly reactive We were previously filtering out `ref.current` dependencies in propagateScopeDependencies:checkValidDependency`. This is incorrect. Instead, we now always take a dependency on ref values (the outer box) as they may be reactive. Pruning is done in pruneNonReactiveDependencies. This PR includes a small patch to `collectReactiveIdentifier`. Prior to this, we conservatively assumed that pruned scopes always produced reactive declarations. This assumption fixed a bug with non-reactivity, but some of these declarations are `useRef` calls. Now we have special handling for this case ```js // This often produces a pruned scope React.useRef(1); ``` --- .../src/HIR/PropagateScopeDependenciesHIR.ts | 18 ++-- .../CollectReactiveIdentifiers.ts | 14 ++- .../compiler/bug-nonreactive-ref.expect.md | 89 --------------- .../fixtures/compiler/bug-nonreactive-ref.tsx | 33 ------ .../compiler/reactive-ref-param.expect.md | 102 ++++++++++++++++++ .../fixtures/compiler/reactive-ref-param.tsx | 29 +++++ .../fixtures/compiler/reactive-ref.expect.md | 79 ++++++++++++++ .../fixtures/compiler/reactive-ref.tsx | 22 ++++ .../ref-parameter-mutate-in-effect.expect.md | 32 +++--- .../ref-parameter-mutate-in-effect.js | 2 +- .../packages/snap/src/SproutTodoFilter.ts | 1 - 11 files changed, 273 insertions(+), 148 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.tsx 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 bbec25a57c..7fd44c29dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -440,14 +440,6 @@ class Context { // 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; @@ -549,6 +541,16 @@ class Context { }); } + // ref.current access is not a valid dep + if ( + isUseRefType(maybeDependency.identifier) && + maybeDependency.path.at(0)?.property === 'current' + ) { + maybeDependency = { + identifier: maybeDependency.identifier, + path: [], + }; + } if (this.#checkValidDependency(maybeDependency)) { this.#dependencies.value!.push(maybeDependency); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts index 610e93965f..3851234005 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts @@ -12,6 +12,8 @@ import { PrunedReactiveScopeBlock, ReactiveFunction, isPrimitiveType, + isUseRefType, + Identifier, } from '../HIR/HIR'; import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; @@ -50,13 +52,21 @@ class Visitor extends ReactiveFunctionVisitor> { this.traversePrunedScope(scopeBlock, state); for (const [id, decl] of scopeBlock.scope.declarations) { - if (!isPrimitiveType(decl.identifier)) { + if ( + !isPrimitiveType(decl.identifier) && + !isStableRefType(decl.identifier, state) + ) { state.add(id); } } } } - +function isStableRefType( + identifier: Identifier, + reactiveIdentifiers: Set, +): boolean { + return isUseRefType(identifier) && !reactiveIdentifiers.has(identifier.id); +} /* * Computes a set of identifiers which are reactive, using the analysis previously performed * in `InferReactivePlaces`. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md deleted file mode 100644 index f79df15d52..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md +++ /dev/null @@ -1,89 +0,0 @@ - -## Input - -```javascript -import {useRef} from 'react'; -import {Stringify} from 'shared-runtime'; - -/** - * Bug: we're currently filtering out `ref.current` dependencies in - * `propagateScopeDependencies:checkValidDependency`. This is incorrect. - * Instead, we should always take a dependency on ref values (the outer box) as - * they may be reactive. Pruning should be done in - * `pruneNonReactiveDependencies` - * - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
- * Forget: - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- */ -function Component({cond}) { - const ref1 = useRef(1); - const ref2 = useRef(2); - const ref = cond ? ref1 : ref2; - const cb = () => ref.current; - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{cond: true}], - sequentialRenders: [{cond: true}, {cond: false}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import { useRef } from "react"; -import { Stringify } from "shared-runtime"; - -/** - * Bug: we're currently filtering out `ref.current` dependencies in - * `propagateScopeDependencies:checkValidDependency`. This is incorrect. - * Instead, we should always take a dependency on ref values (the outer box) as - * they may be reactive. Pruning should be done in - * `pruneNonReactiveDependencies` - * - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
- * Forget: - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- */ -function Component(t0) { - const $ = _c(1); - const { cond } = t0; - const ref1 = useRef(1); - const ref2 = useRef(2); - const ref = cond ? ref1 : ref2; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const cb = () => ref.current; - t1 = ; - $[0] = t1; - } else { - t1 = $[0]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ cond: true }], - sequentialRenders: [{ cond: true }, { cond: false }], -}; - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx deleted file mode 100644 index a0115f2df3..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import {useRef} from 'react'; -import {Stringify} from 'shared-runtime'; - -/** - * Bug: we're currently filtering out `ref.current` dependencies in - * `propagateScopeDependencies:checkValidDependency`. This is incorrect. - * Instead, we should always take a dependency on ref values (the outer box) as - * they may be reactive. Pruning should be done in - * `pruneNonReactiveDependencies` - * - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
- * Forget: - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- */ -function Component({cond}) { - const ref1 = useRef(1); - const ref2 = useRef(2); - const ref = cond ? ref1 : ref2; - const cb = () => ref.current; - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{cond: true}], - sequentialRenders: [{cond: true}, {cond: false}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md new file mode 100644 index 0000000000..c8922ab0c4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md @@ -0,0 +1,102 @@ + +## Input + +```javascript +import {useRef, forwardRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ + +function Parent({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + return ; +} + +function ChildImpl(_props, ref) { + const cb = () => ref.current; + return ; +} + +const Child = forwardRef(ChildImpl); + +export const FIXTURE_ENTRYPOINT = { + fn: Parent, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useRef, forwardRef } from "react"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ + +function Parent(t0) { + const $ = _c(2); + const { cond } = t0; + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + let t1; + if ($[0] !== ref) { + t1 = ; + $[0] = ref; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +function ChildImpl(_props, ref) { + const $ = _c(4); + let t0; + if ($[0] !== ref) { + t0 = () => ref.current; + $[0] = ref; + $[1] = t0; + } else { + t0 = $[1]; + } + const cb = t0; + let t1; + if ($[2] !== cb) { + t1 = ; + $[2] = cb; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +const Child = forwardRef(ChildImpl); + +export const FIXTURE_ENTRYPOINT = { + fn: Parent, + params: [{ cond: true }], + sequentialRenders: [{ cond: true }, { cond: false }], +}; + +``` + +### Eval output +(kind: ok)
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+
{"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/reactive-ref-param.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.tsx new file mode 100644 index 0000000000..c3eea7a243 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.tsx @@ -0,0 +1,29 @@ +import {useRef, forwardRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ + +function Parent({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + return ; +} + +function ChildImpl(_props, ref) { + const cb = () => ref.current; + return ; +} + +const Child = forwardRef(ChildImpl); + +export const FIXTURE_ENTRYPOINT = { + fn: Parent, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md new file mode 100644 index 0000000000..c3876c2292 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md @@ -0,0 +1,79 @@ + +## Input + +```javascript +import {useRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ +function Component({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + const cb = () => ref.current; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useRef } from "react"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ +function Component(t0) { + const $ = _c(4); + const { cond } = t0; + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + let t1; + if ($[0] !== ref) { + t1 = () => ref.current; + $[0] = ref; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = ; + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], + sequentialRenders: [{ cond: true }, { cond: false }], +}; + +``` + +### Eval output +(kind: ok)
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+
{"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/reactive-ref.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.tsx new file mode 100644 index 0000000000..db95c5c204 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.tsx @@ -0,0 +1,22 @@ +import {useRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ +function Component({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + const cb = () => ref.current; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; 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..7a10cfe48e 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 @@ -13,7 +13,7 @@ function Foo(props, ref) { export const FIXTURE_ENTRYPOINT = { fn: Foo, - params: [{bar: 'foo'}, {ref: {cuurrent: 1}}], + params: [{bar: 'foo'}, {ref: {current: 1}}], isComponent: true, }; @@ -26,35 +26,39 @@ 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; } export const FIXTURE_ENTRYPOINT = { fn: Foo, - params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }], + params: [{ bar: "foo" }, { ref: { current: 1 } }], isComponent: true, }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js index c60141e8d5..ffd9eae08d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js @@ -9,6 +9,6 @@ function Foo(props, ref) { export const FIXTURE_ENTRYPOINT = { fn: Foo, - params: [{bar: 'foo'}, {ref: {cuurrent: 1}}], + params: [{bar: 'foo'}, {ref: {current: 1}}], isComponent: true, }; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 6c136f6310..1971fe0d33 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -484,7 +484,6 @@ const skipFilter = new Set([ 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', - 'bug-nonreactive-ref', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond', From 366938753f68b0085a89309dc04ce4451b197993 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 12 Nov 2024 14:05:24 -0500 Subject: [PATCH 103/422] [compiler] Fix: ref.current now correctly reactive We were previously filtering out `ref.current` dependencies in propagateScopeDependencies:checkValidDependency`. This is incorrect. Instead, we now always take a dependency on ref values (the outer box) as they may be reactive. Pruning is done in pruneNonReactiveDependencies. This PR includes a small patch to `collectReactiveIdentifier`. Prior to this, we conservatively assumed that pruned scopes always produced reactive declarations. This assumption fixed a bug with non-reactivity, but some of these declarations are `useRef` calls. Now we have special handling for this case ```js // This often produces a pruned scope React.useRef(1); ``` --- .../src/HIR/PropagateScopeDependenciesHIR.ts | 18 ++-- .../CollectReactiveIdentifiers.ts | 14 ++- .../compiler/bug-nonreactive-ref.expect.md | 89 --------------- .../fixtures/compiler/bug-nonreactive-ref.tsx | 33 ------ .../compiler/reactive-ref-param.expect.md | 102 ++++++++++++++++++ .../fixtures/compiler/reactive-ref-param.tsx | 29 +++++ .../fixtures/compiler/reactive-ref.expect.md | 79 ++++++++++++++ .../fixtures/compiler/reactive-ref.tsx | 22 ++++ .../ref-parameter-mutate-in-effect.expect.md | 32 +++--- .../ref-parameter-mutate-in-effect.js | 2 +- .../packages/snap/src/SproutTodoFilter.ts | 1 - 11 files changed, 273 insertions(+), 148 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.tsx 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 bbec25a57c..7fd44c29dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -440,14 +440,6 @@ class Context { // 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; @@ -549,6 +541,16 @@ class Context { }); } + // ref.current access is not a valid dep + if ( + isUseRefType(maybeDependency.identifier) && + maybeDependency.path.at(0)?.property === 'current' + ) { + maybeDependency = { + identifier: maybeDependency.identifier, + path: [], + }; + } if (this.#checkValidDependency(maybeDependency)) { this.#dependencies.value!.push(maybeDependency); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts index 610e93965f..3851234005 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts @@ -12,6 +12,8 @@ import { PrunedReactiveScopeBlock, ReactiveFunction, isPrimitiveType, + isUseRefType, + Identifier, } from '../HIR/HIR'; import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; @@ -50,13 +52,21 @@ class Visitor extends ReactiveFunctionVisitor> { this.traversePrunedScope(scopeBlock, state); for (const [id, decl] of scopeBlock.scope.declarations) { - if (!isPrimitiveType(decl.identifier)) { + if ( + !isPrimitiveType(decl.identifier) && + !isStableRefType(decl.identifier, state) + ) { state.add(id); } } } } - +function isStableRefType( + identifier: Identifier, + reactiveIdentifiers: Set, +): boolean { + return isUseRefType(identifier) && !reactiveIdentifiers.has(identifier.id); +} /* * Computes a set of identifiers which are reactive, using the analysis previously performed * in `InferReactivePlaces`. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md deleted file mode 100644 index f79df15d52..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md +++ /dev/null @@ -1,89 +0,0 @@ - -## Input - -```javascript -import {useRef} from 'react'; -import {Stringify} from 'shared-runtime'; - -/** - * Bug: we're currently filtering out `ref.current` dependencies in - * `propagateScopeDependencies:checkValidDependency`. This is incorrect. - * Instead, we should always take a dependency on ref values (the outer box) as - * they may be reactive. Pruning should be done in - * `pruneNonReactiveDependencies` - * - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
- * Forget: - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- */ -function Component({cond}) { - const ref1 = useRef(1); - const ref2 = useRef(2); - const ref = cond ? ref1 : ref2; - const cb = () => ref.current; - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{cond: true}], - sequentialRenders: [{cond: true}, {cond: false}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import { useRef } from "react"; -import { Stringify } from "shared-runtime"; - -/** - * Bug: we're currently filtering out `ref.current` dependencies in - * `propagateScopeDependencies:checkValidDependency`. This is incorrect. - * Instead, we should always take a dependency on ref values (the outer box) as - * they may be reactive. Pruning should be done in - * `pruneNonReactiveDependencies` - * - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
- * Forget: - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- */ -function Component(t0) { - const $ = _c(1); - const { cond } = t0; - const ref1 = useRef(1); - const ref2 = useRef(2); - const ref = cond ? ref1 : ref2; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const cb = () => ref.current; - t1 = ; - $[0] = t1; - } else { - t1 = $[0]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ cond: true }], - sequentialRenders: [{ cond: true }, { cond: false }], -}; - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx deleted file mode 100644 index a0115f2df3..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import {useRef} from 'react'; -import {Stringify} from 'shared-runtime'; - -/** - * Bug: we're currently filtering out `ref.current` dependencies in - * `propagateScopeDependencies:checkValidDependency`. This is incorrect. - * Instead, we should always take a dependency on ref values (the outer box) as - * they may be reactive. Pruning should be done in - * `pruneNonReactiveDependencies` - * - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
- * Forget: - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- */ -function Component({cond}) { - const ref1 = useRef(1); - const ref2 = useRef(2); - const ref = cond ? ref1 : ref2; - const cb = () => ref.current; - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{cond: true}], - sequentialRenders: [{cond: true}, {cond: false}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md new file mode 100644 index 0000000000..c8922ab0c4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md @@ -0,0 +1,102 @@ + +## Input + +```javascript +import {useRef, forwardRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ + +function Parent({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + return ; +} + +function ChildImpl(_props, ref) { + const cb = () => ref.current; + return ; +} + +const Child = forwardRef(ChildImpl); + +export const FIXTURE_ENTRYPOINT = { + fn: Parent, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useRef, forwardRef } from "react"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ + +function Parent(t0) { + const $ = _c(2); + const { cond } = t0; + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + let t1; + if ($[0] !== ref) { + t1 = ; + $[0] = ref; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +function ChildImpl(_props, ref) { + const $ = _c(4); + let t0; + if ($[0] !== ref) { + t0 = () => ref.current; + $[0] = ref; + $[1] = t0; + } else { + t0 = $[1]; + } + const cb = t0; + let t1; + if ($[2] !== cb) { + t1 = ; + $[2] = cb; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +const Child = forwardRef(ChildImpl); + +export const FIXTURE_ENTRYPOINT = { + fn: Parent, + params: [{ cond: true }], + sequentialRenders: [{ cond: true }, { cond: false }], +}; + +``` + +### Eval output +(kind: ok)
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+
{"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/reactive-ref-param.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.tsx new file mode 100644 index 0000000000..c3eea7a243 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.tsx @@ -0,0 +1,29 @@ +import {useRef, forwardRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ + +function Parent({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + return ; +} + +function ChildImpl(_props, ref) { + const cb = () => ref.current; + return ; +} + +const Child = forwardRef(ChildImpl); + +export const FIXTURE_ENTRYPOINT = { + fn: Parent, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md new file mode 100644 index 0000000000..c3876c2292 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md @@ -0,0 +1,79 @@ + +## Input + +```javascript +import {useRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ +function Component({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + const cb = () => ref.current; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useRef } from "react"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ +function Component(t0) { + const $ = _c(4); + const { cond } = t0; + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + let t1; + if ($[0] !== ref) { + t1 = () => ref.current; + $[0] = ref; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = ; + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], + sequentialRenders: [{ cond: true }, { cond: false }], +}; + +``` + +### Eval output +(kind: ok)
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+
{"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/reactive-ref.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.tsx new file mode 100644 index 0000000000..db95c5c204 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.tsx @@ -0,0 +1,22 @@ +import {useRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ +function Component({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + const cb = () => ref.current; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; 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..7a10cfe48e 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 @@ -13,7 +13,7 @@ function Foo(props, ref) { export const FIXTURE_ENTRYPOINT = { fn: Foo, - params: [{bar: 'foo'}, {ref: {cuurrent: 1}}], + params: [{bar: 'foo'}, {ref: {current: 1}}], isComponent: true, }; @@ -26,35 +26,39 @@ 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; } export const FIXTURE_ENTRYPOINT = { fn: Foo, - params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }], + params: [{ bar: "foo" }, { ref: { current: 1 } }], isComponent: true, }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js index c60141e8d5..ffd9eae08d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js @@ -9,6 +9,6 @@ function Foo(props, ref) { export const FIXTURE_ENTRYPOINT = { fn: Foo, - params: [{bar: 'foo'}, {ref: {cuurrent: 1}}], + params: [{bar: 'foo'}, {ref: {current: 1}}], isComponent: true, }; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 6c136f6310..1971fe0d33 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -484,7 +484,6 @@ const skipFilter = new Set([ 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', - 'bug-nonreactive-ref', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond', From 781222610f3eee098dbd6e4ba548e552ba4717cd Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 12 Nov 2024 14:05:31 -0500 Subject: [PATCH 104/422] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 69 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...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 | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- ...unction-uncond-optionals-hoisted.expect.md | 4 +- 12 files changed, 160 insertions(+), 171 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 1d2e155848..855bc039ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,6 +231,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 7fd44c29dc..8aed17f8ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -663,35 +663,54 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); - 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); - } + 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 (!processedInstrsInOptional.has(instr)) { + handleInstruction(instr, context); + } + } - // 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); + if (!processedInstrsInOptional.has(block.terminal)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr)) { - handleInstruction(instr, context); - } - } + }; - if (!processedInstrsInOptional.has(block.terminal)) { - 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/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..9ce4a62e71 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]; + let t2; + if ($[1] !== a || $[2] !== x) { + t2 = () => [a, x]; $[1] = a; - $[2] = t2; - $[3] = t3; + $[2] = x; + $[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/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]; From 125581ed9ab2c6a6c45936e4000ed904758f3e5b Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 15 Nov 2024 12:02:15 -0500 Subject: [PATCH 105/422] [compiler] Fix: ref.current now correctly reactive We were previously filtering out `ref.current` dependencies in propagateScopeDependencies:checkValidDependency`. This is incorrect. Instead, we now always take a dependency on ref values (the outer box) as they may be reactive. Pruning is done in pruneNonReactiveDependencies. This PR includes a small patch to `collectReactiveIdentifier`. Prior to this, we conservatively assumed that pruned scopes always produced reactive declarations. This assumption fixed a bug with non-reactivity, but some of these declarations are `useRef` calls. Now we have special handling for this case ```js // This often produces a pruned scope React.useRef(1); ``` --- .../src/HIR/PropagateScopeDependenciesHIR.ts | 18 ++-- .../CollectReactiveIdentifiers.ts | 14 ++- .../compiler/bug-nonreactive-ref.expect.md | 89 --------------- .../fixtures/compiler/bug-nonreactive-ref.tsx | 33 ------ .../compiler/reactive-ref-param.expect.md | 102 ++++++++++++++++++ .../fixtures/compiler/reactive-ref-param.tsx | 29 +++++ .../fixtures/compiler/reactive-ref.expect.md | 79 ++++++++++++++ .../fixtures/compiler/reactive-ref.tsx | 22 ++++ .../ref-parameter-mutate-in-effect.expect.md | 32 +++--- .../ref-parameter-mutate-in-effect.js | 2 +- .../packages/snap/src/SproutTodoFilter.ts | 1 - 11 files changed, 273 insertions(+), 148 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.tsx 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 bbec25a57c..7fd44c29dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -440,14 +440,6 @@ class Context { // 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; @@ -549,6 +541,16 @@ class Context { }); } + // ref.current access is not a valid dep + if ( + isUseRefType(maybeDependency.identifier) && + maybeDependency.path.at(0)?.property === 'current' + ) { + maybeDependency = { + identifier: maybeDependency.identifier, + path: [], + }; + } if (this.#checkValidDependency(maybeDependency)) { this.#dependencies.value!.push(maybeDependency); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts index 610e93965f..3851234005 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts @@ -12,6 +12,8 @@ import { PrunedReactiveScopeBlock, ReactiveFunction, isPrimitiveType, + isUseRefType, + Identifier, } from '../HIR/HIR'; import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; @@ -50,13 +52,21 @@ class Visitor extends ReactiveFunctionVisitor> { this.traversePrunedScope(scopeBlock, state); for (const [id, decl] of scopeBlock.scope.declarations) { - if (!isPrimitiveType(decl.identifier)) { + if ( + !isPrimitiveType(decl.identifier) && + !isStableRefType(decl.identifier, state) + ) { state.add(id); } } } } - +function isStableRefType( + identifier: Identifier, + reactiveIdentifiers: Set, +): boolean { + return isUseRefType(identifier) && !reactiveIdentifiers.has(identifier.id); +} /* * Computes a set of identifiers which are reactive, using the analysis previously performed * in `InferReactivePlaces`. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md deleted file mode 100644 index f79df15d52..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.expect.md +++ /dev/null @@ -1,89 +0,0 @@ - -## Input - -```javascript -import {useRef} from 'react'; -import {Stringify} from 'shared-runtime'; - -/** - * Bug: we're currently filtering out `ref.current` dependencies in - * `propagateScopeDependencies:checkValidDependency`. This is incorrect. - * Instead, we should always take a dependency on ref values (the outer box) as - * they may be reactive. Pruning should be done in - * `pruneNonReactiveDependencies` - * - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
- * Forget: - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- */ -function Component({cond}) { - const ref1 = useRef(1); - const ref2 = useRef(2); - const ref = cond ? ref1 : ref2; - const cb = () => ref.current; - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{cond: true}], - sequentialRenders: [{cond: true}, {cond: false}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import { useRef } from "react"; -import { Stringify } from "shared-runtime"; - -/** - * Bug: we're currently filtering out `ref.current` dependencies in - * `propagateScopeDependencies:checkValidDependency`. This is incorrect. - * Instead, we should always take a dependency on ref values (the outer box) as - * they may be reactive. Pruning should be done in - * `pruneNonReactiveDependencies` - * - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
- * Forget: - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- */ -function Component(t0) { - const $ = _c(1); - const { cond } = t0; - const ref1 = useRef(1); - const ref2 = useRef(2); - const ref = cond ? ref1 : ref2; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const cb = () => ref.current; - t1 = ; - $[0] = t1; - } else { - t1 = $[0]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ cond: true }], - sequentialRenders: [{ cond: true }, { cond: false }], -}; - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx deleted file mode 100644 index a0115f2df3..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-nonreactive-ref.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import {useRef} from 'react'; -import {Stringify} from 'shared-runtime'; - -/** - * Bug: we're currently filtering out `ref.current` dependencies in - * `propagateScopeDependencies:checkValidDependency`. This is incorrect. - * Instead, we should always take a dependency on ref values (the outer box) as - * they may be reactive. Pruning should be done in - * `pruneNonReactiveDependencies` - * - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
- * Forget: - * (kind: ok) - *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- *
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
- */ -function Component({cond}) { - const ref1 = useRef(1); - const ref2 = useRef(2); - const ref = cond ? ref1 : ref2; - const cb = () => ref.current; - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{cond: true}], - sequentialRenders: [{cond: true}, {cond: false}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md new file mode 100644 index 0000000000..c8922ab0c4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.expect.md @@ -0,0 +1,102 @@ + +## Input + +```javascript +import {useRef, forwardRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ + +function Parent({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + return ; +} + +function ChildImpl(_props, ref) { + const cb = () => ref.current; + return ; +} + +const Child = forwardRef(ChildImpl); + +export const FIXTURE_ENTRYPOINT = { + fn: Parent, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useRef, forwardRef } from "react"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ + +function Parent(t0) { + const $ = _c(2); + const { cond } = t0; + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + let t1; + if ($[0] !== ref) { + t1 = ; + $[0] = ref; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +function ChildImpl(_props, ref) { + const $ = _c(4); + let t0; + if ($[0] !== ref) { + t0 = () => ref.current; + $[0] = ref; + $[1] = t0; + } else { + t0 = $[1]; + } + const cb = t0; + let t1; + if ($[2] !== cb) { + t1 = ; + $[2] = cb; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +const Child = forwardRef(ChildImpl); + +export const FIXTURE_ENTRYPOINT = { + fn: Parent, + params: [{ cond: true }], + sequentialRenders: [{ cond: true }, { cond: false }], +}; + +``` + +### Eval output +(kind: ok)
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+
{"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/reactive-ref-param.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.tsx new file mode 100644 index 0000000000..c3eea7a243 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref-param.tsx @@ -0,0 +1,29 @@ +import {useRef, forwardRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ + +function Parent({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + return ; +} + +function ChildImpl(_props, ref) { + const cb = () => ref.current; + return ; +} + +const Child = forwardRef(ChildImpl); + +export const FIXTURE_ENTRYPOINT = { + fn: Parent, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md new file mode 100644 index 0000000000..c3876c2292 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.expect.md @@ -0,0 +1,79 @@ + +## Input + +```javascript +import {useRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ +function Component({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + const cb = () => ref.current; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useRef } from "react"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ +function Component(t0) { + const $ = _c(4); + const { cond } = t0; + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + let t1; + if ($[0] !== ref) { + t1 = () => ref.current; + $[0] = ref; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = ; + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: true }], + sequentialRenders: [{ cond: true }, { cond: false }], +}; + +``` + +### Eval output +(kind: ok)
{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+
{"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/reactive-ref.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.tsx new file mode 100644 index 0000000000..db95c5c204 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-ref.tsx @@ -0,0 +1,22 @@ +import {useRef} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * Fixture showing that Ref types may be reactive. + * We should always take a dependency on ref values (the outer box) as + * they may be reactive. Pruning should be done in + * `pruneNonReactiveDependencies` + */ +function Component({cond}) { + const ref1 = useRef(1); + const ref2 = useRef(2); + const ref = cond ? ref1 : ref2; + const cb = () => ref.current; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: true}], + sequentialRenders: [{cond: true}, {cond: false}], +}; 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..7a10cfe48e 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 @@ -13,7 +13,7 @@ function Foo(props, ref) { export const FIXTURE_ENTRYPOINT = { fn: Foo, - params: [{bar: 'foo'}, {ref: {cuurrent: 1}}], + params: [{bar: 'foo'}, {ref: {current: 1}}], isComponent: true, }; @@ -26,35 +26,39 @@ 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; } export const FIXTURE_ENTRYPOINT = { fn: Foo, - params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }], + params: [{ bar: "foo" }, { ref: { current: 1 } }], isComponent: true, }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js index c60141e8d5..ffd9eae08d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.js @@ -9,6 +9,6 @@ function Foo(props, ref) { export const FIXTURE_ENTRYPOINT = { fn: Foo, - params: [{bar: 'foo'}, {ref: {cuurrent: 1}}], + params: [{bar: 'foo'}, {ref: {current: 1}}], isComponent: true, }; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 6c136f6310..1971fe0d33 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -484,7 +484,6 @@ const skipFilter = new Set([ 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', - 'bug-nonreactive-ref', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond', From 6483117723e785c47bf1356140ec6419f64b5b44 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 15 Nov 2024 12:02:15 -0500 Subject: [PATCH 106/422] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 69 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...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 | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- ...unction-uncond-optionals-hoisted.expect.md | 4 +- 12 files changed, 160 insertions(+), 171 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 1d2e155848..855bc039ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,6 +231,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 7fd44c29dc..8aed17f8ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -663,35 +663,54 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); - 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); - } + 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 (!processedInstrsInOptional.has(instr)) { + handleInstruction(instr, context); + } + } - // 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); + if (!processedInstrsInOptional.has(block.terminal)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr)) { - handleInstruction(instr, context); - } - } + }; - if (!processedInstrsInOptional.has(block.terminal)) { - 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/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..9ce4a62e71 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]; + let t2; + if ($[1] !== a || $[2] !== x) { + t2 = () => [a, x]; $[1] = a; - $[2] = t2; - $[3] = t3; + $[2] = x; + $[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/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]; From 6a143e5a4379741c8f80264201d682e3926f01eb Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 15 Nov 2024 12:02:15 -0500 Subject: [PATCH 107/422] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 9494436d1f..ecc22365dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 1971fe0d33..9a629ed8bb 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 83a96d32c4606ff693f77a802aefca1c3262c3ad Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 15 Nov 2024 12:02:15 -0500 Subject: [PATCH 108/422] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index 14532562fb..9a98f76b97 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] !== bar || $[1] !== foo) { 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] = bar; $[1] = foo; $[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 9a629ed8bb..f1f5ef0f6b 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 66179075578e244d52f147775396ef2043a8c5e2 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 15 Nov 2024 12:02:15 -0500 Subject: [PATCH 109/422] [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 fc33710272b154caee0eacf2251f03ae43f66ced Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 18 Nov 2024 11:17:55 -0500 Subject: [PATCH 110/422] [compiler] repro for type inference + control flow bug Repro for bug in our type inference system. We currently propagate inferred types through control flow / potential type guards. Note that this is inconsistent with both [Flow](https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA) and [Typescript](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA) --- .../bug-type-inference-control-flow.expect.md | 114 ++++++++++++++++++ .../bug-type-inference-control-flow.ts | 41 +++++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + .../snap/src/sprout/shared-runtime.ts | 3 +- 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md new file mode 100644 index 0000000000..3f7c5eeecf --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md @@ -0,0 +1,114 @@ + +## Input + +```javascript +import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime'; + +/** + * Repro for bug in our type inference system. We currently propagate inferred + * types through control flow / potential type guards. Note that this is + * inconsistent with both Flow and Typescript. + * https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA + * https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * [2] + * [3] + * Forget: + * (kind: ok) + * [2] + * [2,3] + */ +function useFoo({cond, value}: {cond: boolean; value: number}) { + const x = {value: cond ? CONST_NUMBER0 : []}; + mutate(x); + + const xValue = x.value; + let result; + if (typeof xValue === 'number') { + result = xValue + 1; // (1) here we infer xValue is a primitive + } else { + result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references + } + + return result; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{cond: true}], + sequentialRenders: [ + {cond: false, value: 2}, + {cond: false, value: 3}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { arrayPush, CONST_NUMBER0, mutate } from "shared-runtime"; + +/** + * Repro for bug in our type inference system. We currently propagate inferred + * types through control flow / potential type guards. Note that this is + * inconsistent with both Flow and Typescript. + * https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA + * https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * [2] + * [3] + * Forget: + * (kind: ok) + * [2] + * [2,3] + */ +function useFoo(t0) { + const $ = _c(5); + const { cond, value } = t0; + let x; + if ($[0] !== cond) { + x = { value: cond ? CONST_NUMBER0 : [] }; + mutate(x); + $[0] = cond; + $[1] = x; + } else { + x = $[1]; + } + + const xValue = x.value; + let result; + if (typeof xValue === "number") { + result = xValue + 1; + } else { + let t1; + if ($[2] !== value || $[3] !== xValue) { + t1 = arrayPush(xValue, value); + $[2] = value; + $[3] = xValue; + $[4] = t1; + } else { + t1 = $[4]; + } + result = t1; + } + return result; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ cond: true }], + sequentialRenders: [ + { cond: false, value: 2 }, + { cond: false, value: 3 }, + ], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts new file mode 100644 index 0000000000..2ba99b048e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts @@ -0,0 +1,41 @@ +import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime'; + +/** + * Repro for bug in our type inference system. We currently propagate inferred + * types through control flow / potential type guards. Note that this is + * inconsistent with both Flow and Typescript. + * https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA + * https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * [2] + * [3] + * Forget: + * (kind: ok) + * [2] + * [2,3] + */ +function useFoo({cond, value}: {cond: boolean; value: number}) { + const x = {value: cond ? CONST_NUMBER0 : []}; + mutate(x); + + const xValue = x.value; + let result; + if (typeof xValue === 'number') { + result = xValue + 1; // (1) here we infer xValue is a primitive + } else { + result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references + } + + return result; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{cond: true}], + sequentialRenders: [ + {cond: false, value: 2}, + {cond: false, value: 3}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index f1f5ef0f6b..bb51ece12f 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -485,6 +485,7 @@ const skipFilter = new Set([ 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', + 'bug-type-inference-control-flow', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index e6e82d6b77..bd069ae6d0 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -107,8 +107,9 @@ export function setPropertyByKey< return arg; } -export function arrayPush(arr: Array, ...values: Array): void { +export function arrayPush(arr: Array, ...values: Array): Array { arr.push(...values); + return arr; } export function graphql(value: string): string { From d4fba739ba32b66acc409680972bbebaf4d0a895 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 18 Nov 2024 11:17:55 -0500 Subject: [PATCH 111/422] [compiler] repro for type inference + control flow bug Repro for bug in our type inference system. We currently propagate inferred types through control flow / potential type guards. Note that this is inconsistent with both [Flow](https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA) and [Typescript](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA) --- .../bug-type-inference-control-flow.expect.md | 114 ++++++++++++++++++ .../bug-type-inference-control-flow.ts | 41 +++++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + .../snap/src/sprout/shared-runtime.ts | 3 +- 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md new file mode 100644 index 0000000000..8fc049fa6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md @@ -0,0 +1,114 @@ + +## Input + +```javascript +import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime'; + +/** + * Repro for bug in our type inference system. We currently propagate inferred + * types through control flow / potential type guards. Note that this is + * inconsistent with both Flow and Typescript. + * https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA + * https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * [2] + * [3] + * Forget: + * (kind: ok) + * [2] + * [2,3] + */ +function useFoo({cond, value}: {cond: boolean; value: number}) { + const x = {value: cond ? CONST_NUMBER0 : []}; + mutate(x); + + const xValue = x.value; + let result; + if (typeof xValue === 'number') { + result = xValue + 1; // (1) here we infer xValue is a primitive + } else { + result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references + } + + return result; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{cond: true}], + sequentialRenders: [ + {cond: false, value: 2}, + {cond: false, value: 3}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { arrayPush, CONST_NUMBER0, mutate } from "shared-runtime"; + +/** + * Repro for bug in our type inference system. We currently propagate inferred + * types through control flow / potential type guards. Note that this is + * inconsistent with both Flow and Typescript. + * https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA + * https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * [2] + * [3] + * Forget: + * (kind: ok) + * [2] + * [2,3] + */ +function useFoo(t0) { + const $ = _c(5); + const { cond, value } = t0; + let x; + if ($[0] !== cond) { + x = { value: cond ? CONST_NUMBER0 : [] }; + mutate(x); + $[0] = cond; + $[1] = x; + } else { + x = $[1]; + } + + const xValue = x.value; + let result; + if (typeof xValue === "number") { + result = xValue + 1; + } else { + let t1; + if ($[2] !== value || $[3] !== xValue) { + t1 = arrayPush(xValue, value); + $[2] = value; + $[3] = xValue; + $[4] = t1; + } else { + t1 = $[4]; + } + result = t1; + } + return result; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ cond: true }], + sequentialRenders: [ + { cond: false, value: 2 }, + { cond: false, value: 3 }, + ], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts new file mode 100644 index 0000000000..4b8957dc8d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts @@ -0,0 +1,41 @@ +import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime'; + +/** + * Repro for bug in our type inference system. We currently propagate inferred + * types through control flow / potential type guards. Note that this is + * inconsistent with both Flow and Typescript. + * https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA + * https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * [2] + * [3] + * Forget: + * (kind: ok) + * [2] + * [2,3] + */ +function useFoo({cond, value}: {cond: boolean; value: number}) { + const x = {value: cond ? CONST_NUMBER0 : []}; + mutate(x); + + const xValue = x.value; + let result; + if (typeof xValue === 'number') { + result = xValue + 1; // (1) here we infer xValue is a primitive + } else { + result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references + } + + return result; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{cond: true}], + sequentialRenders: [ + {cond: false, value: 2}, + {cond: false, value: 3}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index f1f5ef0f6b..bb51ece12f 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -485,6 +485,7 @@ const skipFilter = new Set([ 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', + 'bug-type-inference-control-flow', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index e6e82d6b77..bd069ae6d0 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -107,8 +107,9 @@ export function setPropertyByKey< return arg; } -export function arrayPush(arr: Array, ...values: Array): void { +export function arrayPush(arr: Array, ...values: Array): Array { arr.push(...values); + return arr; } export function graphql(value: string): string { From 8fb1dc42f6874637dcd4dd87e0170c67395dbb32 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 18 Nov 2024 12:47:39 -0500 Subject: [PATCH 112/422] [compiler][ez] Clean up duplicate code in propagateScopeDeps Clean up duplicate checks for when to skip processing values as dependencies / hoistable temporaries. --- .../src/HIR/PropagateScopeDependenciesHIR.ts | 82 +++++++++++++------ 1 file changed, 58 insertions(+), 24 deletions(-) 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 8aed17f8ee..4a85a4ef5c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -358,6 +358,7 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + #processedInstrsInOptional: ReadonlySet; /** * Tracks the traversal state. See Context.declare for explanation of why this @@ -368,9 +369,11 @@ class Context { constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, + processedInstrsInOptional: ReadonlySet, ) { this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; this.#temporaries = temporaries; + this.#processedInstrsInOptional = processedInstrsInOptional; } enterScope(scope: ReactiveScope): void { @@ -574,22 +577,49 @@ class Context { currentScope.reassignments.add(place.identifier); } } + enterInnerFn(cb: () => T): T { + const wasInInnerFn = this.inInnerFn; + this.inInnerFn = true; + const result = cb(); + this.inInnerFn = wasInInnerFn; + return result; + } + + /** + * Skip dependencies that are subexpressions of other dependencies. e.g. if a + * dependency is tracked in the temporaries sidemap, it can be added at + * site-of-use + */ + isDeferredDependency( + instr: + | {kind: HIRValue.Instruction; value: Instruction} + | {kind: HIRValue.Terminal; value: Terminal}, + ): boolean { + return ( + this.#processedInstrsInOptional.has(instr.value) || + (instr.kind === HIRValue.Instruction && + this.#temporaries.has(instr.value.lvalue.identifier.id)) + ); + } +} +enum HIRValue { + Instruction = 1, + Terminal, } function handleInstruction(instr: Instruction, context: Context): void { const {id, value, lvalue} = instr; - if (value.kind === 'LoadLocal') { - if ( - value.place.identifier.name === null || - lvalue.identifier.name !== null || - context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (context.isUsedOutsideDeclaringScope(lvalue)) { - context.visitProperty(value.object, value.property, false); - } + context.declare(lvalue.identifier, { + id, + scope: context.currentScope, + }); + if ( + context.isDeferredDependency({kind: HIRValue.Instruction, value: instr}) + ) { + return; + } + if (value.kind === 'PropertyLoad') { + context.visitProperty(value.object, value.property, false); } else if (value.kind === 'StoreLocal') { context.visitOperand(value.value); if (value.lvalue.kind === InstructionKind.Reassign) { @@ -632,11 +662,6 @@ function handleInstruction(instr: Instruction, context: Context): void { context.visitOperand(operand); } } - - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); } function collectDependencies( @@ -645,7 +670,11 @@ function collectDependencies( temporaries: ReadonlyMap, processedInstrsInOptional: ReadonlySet, ): Map> { - const context = new Context(usedOutsideDeclaringScope, temporaries); + const context = new Context( + usedOutsideDeclaringScope, + temporaries, + processedInstrsInOptional, + ); for (const param of fn.params) { if (param.kind === 'Identifier') { @@ -694,16 +723,21 @@ function collectDependencies( /** * 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 (!processedInstrsInOptional.has(instr)) { + const innerFn = instr.value.loweredFunc.func; + context.enterInnerFn(() => { + handleFunction(innerFn); + }); + } else { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal)) { + if ( + !context.isDeferredDependency({ + kind: HIRValue.Terminal, + value: block.terminal, + }) + ) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } From 22902de456698d3be51f247eb84adb7307100c39 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 19 Nov 2024 09:43:04 -0500 Subject: [PATCH 113/422] [compiler] Context variables as dependencies We previously didn't track context variables in the hoistable values sidemap of `propagateScopeDependencies`. This was overly conservative as we *do* track the mutable range of context variables, and it is safe to hoist accesses to context variables after their last direct / aliased maybe-assignment. ```js function Component({value}) { // start of mutable range for `x` let x = DEFAULT; const setX = () => x = value; const aliasedSet = maybeAlias(setX); maybeCall(aliasedSet); // end of mutable range for `x` // here, we should be able to take x (and property reads // off of x) as dependencies return } ``` --- .../src/HIR/HIR.ts | 11 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 65 ++++++--- .../bug-functiondecl-hoisting.expect.md | 18 ++- ...-array-assignment-to-context-var.expect.md | 15 +- ...array-declaration-to-context-var.expect.md | 15 +- ...object-assignment-to-context-var.expect.md | 15 +- ...bject-declaration-to-context-var.expect.md | 15 +- ...mutated-non-reactive-to-reactive.expect.md | 16 +-- ...ures-reassigned-context-property.expect.md | 53 ------- ...ures-reassigned-context-property.expect.md | 101 ++++++++++++++ ...-captures-reassigned-context-property.tsx} | 0 ...o-reordering-depslist-assignment.expect.md | 15 +- ...epro-scope-missing-mutable-range.expect.md | 16 +-- ...l-dependency-on-context-variable.expect.md | 16 +-- .../context-var-granular-dep.expect.md | 130 ++++++++++++++++++ .../context-var-granular-dep.js | 43 ++++++ ...epro-scope-missing-mutable-range.expect.md | 16 +-- .../use-operator-conditional.expect.md | 42 +++--- compiler/packages/snap/src/sprout/index.ts | 10 +- .../snap/src/sprout/shared-runtime.ts | 29 ++-- 20 files changed, 447 insertions(+), 194 deletions(-) delete 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/useCallback-captures-reassigned-context-property.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{error.todo-useCallback-captures-reassigned-context-property.tsx => useCallback-captures-reassigned-context-property.tsx} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 954fb6f400..8cae007ec0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -840,6 +840,11 @@ export type LoadLocal = { place: Place; loc: SourceLocation; }; +export type LoadContext = { + kind: 'LoadContext'; + place: Place; + loc: SourceLocation; +}; /* * The value of a given instruction. Note that values are not recursive: complex @@ -852,11 +857,7 @@ export type LoadLocal = { export type InstructionValue = | LoadLocal - | { - kind: 'LoadContext'; - place: Place; - loc: SourceLocation; - } + | LoadContext | { kind: 'DeclareLocal'; lvalue: LValue; 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 4a85a4ef5c..08856e9143 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -17,6 +17,11 @@ import { areEqualPaths, IdentifierId, Terminal, + InstructionValue, + LoadContext, + TInstruction, + FunctionExpression, + ObjectMethod, } from './HIR'; import { collectHoistablePropertyLoads, @@ -223,11 +228,25 @@ export function collectTemporariesSidemap( fn, usedOutsideDeclaringScope, temporaries, - false, + null, ); return temporaries; } +function isLoadContextMutable( + instrValue: InstructionValue, + id: InstructionId, +): instrValue is LoadContext { + if (instrValue.kind === 'LoadContext') { + CompilerError.invariant(instrValue.place.identifier.scope != null, { + reason: + '[PropagateScopeDependencies] Expected all context variables to be assigned a scope', + loc: instrValue.loc, + }); + return id >= instrValue.place.identifier.scope.range.end; + } + return false; +} /** * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a * function and all nested functions. @@ -239,17 +258,21 @@ function collectTemporariesSidemapImpl( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: Map, - isInnerFn: boolean, + innerFnContext: {instrId: InstructionId} | null, ): void { for (const [_, block] of fn.body.blocks) { - for (const instr of block.instructions) { - const {value, lvalue} = instr; + for (const {value, lvalue, id: origInstrId} of block.instructions) { + const instrId = + innerFnContext != null ? innerFnContext.instrId : origInstrId; const usedOutside = usedOutsideDeclaringScope.has( lvalue.identifier.declarationId, ); if (value.kind === 'PropertyLoad' && !usedOutside) { - if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + if ( + innerFnContext == null || + 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 @@ -265,13 +288,13 @@ function collectTemporariesSidemapImpl( temporaries.set(lvalue.identifier.id, property); } } else if ( - value.kind === 'LoadLocal' && + (value.kind === 'LoadLocal' || isLoadContextMutable(value, instrId)) && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { if ( - !isInnerFn || + innerFnContext == null || fn.context.some( context => context.identifier.id === value.place.identifier.id, ) @@ -289,7 +312,7 @@ function collectTemporariesSidemapImpl( value.loweredFunc.func, usedOutsideDeclaringScope, temporaries, - true, + innerFnContext ?? {instrId}, ); } } @@ -364,7 +387,7 @@ class Context { * Tracks the traversal state. See Context.declare for explanation of why this * is needed. */ - inInnerFn: boolean = false; + #innerFnContext: {outerInstrId: InstructionId} | null = null; constructor( temporariesUsedOutsideScope: ReadonlySet, @@ -434,7 +457,7 @@ class Context { * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { - if (this.inInnerFn) return; + if (this.#innerFnContext != null) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -577,11 +600,14 @@ class Context { currentScope.reassignments.add(place.identifier); } } - enterInnerFn(cb: () => T): T { - const wasInInnerFn = this.inInnerFn; - this.inInnerFn = true; + enterInnerFn( + innerFn: TInstruction | TInstruction, + cb: () => T, + ): T { + const prevContext = this.#innerFnContext; + this.#innerFnContext = this.#innerFnContext ?? {outerInstrId: innerFn.id}; const result = cb(); - this.inInnerFn = wasInInnerFn; + this.#innerFnContext = prevContext; return result; } @@ -724,9 +750,14 @@ function collectDependencies( * Recursively visit the inner function to extract dependencies there */ const innerFn = instr.value.loweredFunc.func; - context.enterInnerFn(() => { - handleFunction(innerFn); - }); + context.enterInnerFn( + instr as + | TInstruction + | TInstruction, + () => { + handleFunction(innerFn); + }, + ); } else { handleInstruction(instr, context); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md index 2b0031b117..f8712ed728 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md @@ -58,18 +58,16 @@ function Foo(t0) { bar = $[1]; result = $[2]; } - - const t1 = bar; - let t2; - if ($[3] !== result || $[4] !== t1) { - t2 = ; - $[3] = result; - $[4] = t1; - $[5] = t2; + let t1; + if ($[3] !== bar || $[4] !== result) { + t1 = ; + $[3] = bar; + $[4] = result; + $[5] = t1; } else { - t2 = $[5]; + t1 = $[5]; } - return t2; + return t1; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md index 7febb3fecb..1268cbcfdc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md @@ -43,16 +43,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = { x: t0 }; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = { x }; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md index 26b56ea2a4..769e4871f4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md @@ -42,16 +42,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 =
{t0}
; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 =
{x}
; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md index 5ffa73389f..e66ef2df13 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md @@ -43,16 +43,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = { x: t0 }; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = { x }; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md index 2c495d8223..66799c5c47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md @@ -42,16 +42,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = { x: t0 }; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = { x }; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { 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..d34db46d6a 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 @@ -33,17 +33,15 @@ function f(a) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 =
; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 =
; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { 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 deleted file mode 100644 index ae44f27912..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,53 +0,0 @@ - -## 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/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 new file mode 100644 index 0000000000..a1cbe89a88 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,101 @@ + +## 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}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @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) { + 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]; + } + let t0; + if ($[2] !== contextVar.val) { + t0 = () => [contextVar.val]; + $[2] = contextVar.val; + $[3] = t0; + } else { + t0 = $[3]; + } + contextVar; + const cb = t0; + let t1; + if ($[4] !== cb) { + t1 = ; + $[4] = cb; + $[5] = t1; + } else { + t1 = $[5]; + } + return t1; +} + +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/error.todo-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 similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx rename to 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/__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..e8a3e2d627 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 @@ -44,16 +44,15 @@ function useFoo(arr1, arr2) { y = $[2]; } let t0; - const t1 = y; - let t2; - if ($[3] !== t1) { - t2 = { y: t1 }; - $[3] = t1; - $[4] = t2; + let t1; + if ($[3] !== y) { + t1 = { y }; + $[3] = y; + $[4] = t1; } else { - t2 = $[4]; + t1 = $[4]; } - t0 = t2; + t0 = t1; return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md index 39f301432e..9d232d8e78 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md @@ -36,17 +36,15 @@ function HomeDiscoStoreItemTileRating(props) { } else { count = $[1]; } - - const t0 = count; - let t1; - if ($[2] !== t0) { - t1 = {t0}; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== count) { + t0 = {count}; + $[2] = count; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md index 23cc7ee846..ceaa350012 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md @@ -67,17 +67,15 @@ function Component(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = [t0]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md new file mode 100644 index 0000000000..d72f34b4fd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md @@ -0,0 +1,130 @@ + +## Input + +```javascript +import {throwErrorWithMessage, ValidateMemoization} from 'shared-runtime'; + +/** + * Context variables are local variables that (1) have at least one reassignment + * and (2) are captured into a function expression. These have a known mutable + * range: from first declaration / assignment to the last direct or aliased, + * mutable reference. + * + * This fixture validates that forget can take granular dependencies on context + * variables when the reference to a context var happens *after* the end of its + * mutable range. + */ +function Component({cond, a}) { + let contextVar; + if (cond) { + contextVar = {val: a}; + } else { + contextVar = {}; + throwErrorWithMessage(''); + } + const cb = {cb: () => contextVar.val * 4}; + + /** + * manually specify input to avoid adding a `PropertyLoad` from contextVar, + * which might affect hoistable-objects analysis. + */ + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: false, a: undefined}], + sequentialRenders: [ + {cond: true, a: 2}, + {cond: true, a: 2}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { throwErrorWithMessage, ValidateMemoization } from "shared-runtime"; + +/** + * Context variables are local variables that (1) have at least one reassignment + * and (2) are captured into a function expression. These have a known mutable + * range: from first declaration / assignment to the last direct or aliased, + * mutable reference. + * + * This fixture validates that forget can take granular dependencies on context + * variables when the reference to a context var happens *after* the end of its + * mutable range. + */ +function Component(t0) { + const $ = _c(10); + const { cond, a } = t0; + let contextVar; + if ($[0] !== a || $[1] !== cond) { + if (cond) { + contextVar = { val: a }; + } else { + contextVar = {}; + throwErrorWithMessage(""); + } + $[0] = a; + $[1] = cond; + $[2] = contextVar; + } else { + contextVar = $[2]; + } + let t1; + if ($[3] !== contextVar.val) { + t1 = { cb: () => contextVar.val * 4 }; + $[3] = contextVar.val; + $[4] = t1; + } else { + t1 = $[4]; + } + const cb = t1; + + const t2 = cond ? a : undefined; + let t3; + if ($[5] !== t2) { + t3 = [t2]; + $[5] = t2; + $[6] = t3; + } else { + t3 = $[6]; + } + let t4; + if ($[7] !== cb || $[8] !== t3) { + t4 = ( + + ); + $[7] = cb; + $[8] = t3; + $[9] = t4; + } else { + t4 = $[9]; + } + return t4; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: false, a: undefined }], + sequentialRenders: [ + { cond: true, a: 2 }, + { cond: true, a: 2 }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[2],"output":{"cb":"[[ function params=0 ]]"}}
+
{"inputs":[2],"output":{"cb":"[[ function params=0 ]]"}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js new file mode 100644 index 0000000000..b9bdd67e2f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js @@ -0,0 +1,43 @@ +import {throwErrorWithMessage, ValidateMemoization} from 'shared-runtime'; + +/** + * Context variables are local variables that (1) have at least one reassignment + * and (2) are captured into a function expression. These have a known mutable + * range: from first declaration / assignment to the last direct or aliased, + * mutable reference. + * + * This fixture validates that forget can take granular dependencies on context + * variables when the reference to a context var happens *after* the end of its + * mutable range. + */ +function Component({cond, a}) { + let contextVar; + if (cond) { + contextVar = {val: a}; + } else { + contextVar = {}; + throwErrorWithMessage(''); + } + const cb = {cb: () => contextVar.val * 4}; + + /** + * manually specify input to avoid adding a `PropertyLoad` from contextVar, + * which might affect hoistable-objects analysis. + */ + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: false, a: undefined}], + sequentialRenders: [ + {cond: true, a: 2}, + {cond: true, a: 2}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md index d8e59c486a..b7c425ba5c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md @@ -35,17 +35,15 @@ function HomeDiscoStoreItemTileRating(props) { } else { count = $[1]; } - - const t0 = count; - let t1; - if ($[2] !== t0) { - t1 = {t0}; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== count) { + t0 = {count}; + $[2] = count; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } ``` 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 d94a5e7e37..e335273026 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 @@ -88,36 +88,34 @@ function Inner(props) { input; input; let t0; - const t1 = input; - let t2; - if ($[0] !== t1) { - t2 = [t1]; - $[0] = t1; - $[1] = t2; + let t1; + if ($[0] !== input) { + t1 = [input]; + $[0] = input; + $[1] = t1; } else { - t2 = $[1]; + t1 = $[1]; } - t0 = t2; + t0 = t1; const output = t0; - const t3 = input; - let t4; - if ($[2] !== t3) { - t4 = [t3]; - $[2] = t3; - $[3] = t4; + let t2; + if ($[2] !== input) { + t2 = [input]; + $[2] = input; + $[3] = t2; } else { - t4 = $[3]; + t2 = $[3]; } - let t5; - if ($[4] !== output || $[5] !== t4) { - t5 = ; + let t3; + if ($[4] !== output || $[5] !== t2) { + t3 = ; $[4] = output; - $[5] = t4; - $[6] = t5; + $[5] = t2; + $[6] = t3; } else { - t5 = $[6]; + t3 = $[6]; } - return t5; + return t3; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/snap/src/sprout/index.ts b/compiler/packages/snap/src/sprout/index.ts index 733be561c0..04748bed28 100644 --- a/compiler/packages/snap/src/sprout/index.ts +++ b/compiler/packages/snap/src/sprout/index.ts @@ -32,7 +32,15 @@ export function runSprout( originalCode: string, forgetCode: string, ): SproutResult { - const forgetResult = doEval(forgetCode); + let forgetResult; + try { + (globalThis as any).__SNAP_EVALUATOR_MODE = 'forget'; + forgetResult = doEval(forgetCode); + } catch (e) { + throw e; + } finally { + (globalThis as any).__SNAP_EVALUATOR_MODE = undefined; + } if (forgetResult.kind === 'UnexpectedError') { return makeError('Unexpected error in Forget runner', forgetResult.value); } diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index e6e82d6b77..5687f26c45 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -258,26 +258,35 @@ export function Throw() { export function ValidateMemoization({ inputs, - output, + output: rawOutput, + onlyCheckCompiled = false, }: { inputs: Array; output: any; + onlyCheckCompiled: boolean; }): React.ReactElement { 'use no forget'; + // Wrap rawOutput as it might be a function, which useState would invoke. + const output = {value: rawOutput}; const [previousInputs, setPreviousInputs] = React.useState(inputs); const [previousOutput, setPreviousOutput] = React.useState(output); if ( - inputs.length !== previousInputs.length || - inputs.some((item, i) => item !== previousInputs[i]) + onlyCheckCompiled && + (globalThis as any).__SNAP_EVALUATOR_MODE === 'forget' ) { - // Some input changed, we expect the output to change - setPreviousInputs(inputs); - setPreviousOutput(output); - } else if (output !== previousOutput) { - // Else output should be stable - throw new Error('Output identity changed but inputs did not'); + if ( + inputs.length !== previousInputs.length || + inputs.some((item, i) => item !== previousInputs[i]) + ) { + // Some input changed, we expect the output to change + setPreviousInputs(inputs); + setPreviousOutput(output); + } else if (output.value !== previousOutput.value) { + // Else output should be stable + throw new Error('Output identity changed but inputs did not'); + } } - return React.createElement(Stringify, {inputs, output}); + return React.createElement(Stringify, {inputs, output: rawOutput}); } export function createHookWrapper( From ffe8e0079a925a08145496234d91e1bb4334226a Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 19 Nov 2024 09:43:04 -0500 Subject: [PATCH 114/422] [compiler][ez] Add shape for global Object.keys Add shape / type for global Object.keys. This is useful because - it has an Effect.Read (not an Effect.Capture) as it cannot alias its argument. - Object.keys return an array --- .../src/HIR/Globals.ts | 15 ++++++ .../compiler/shapes-object-key.expect.md | 49 +++++++++++++++++++ .../fixtures/compiler/shapes-object-key.ts | 11 +++++ 3 files changed, 75 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts index 2525b87bd8..c85de65f06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -87,6 +87,21 @@ const UNTYPED_GLOBALS: Set = new Set([ ]); const TYPED_GLOBALS: Array<[string, BuiltInType]> = [ + [ + 'Object', + addObject(DEFAULT_SHAPES, 'Object', [ + [ + 'keys', + addFunction(DEFAULT_SHAPES, [], { + positionalParams: [Effect.Read], + restParam: null, + returnType: {kind: 'Object', shapeId: BuiltInArrayId}, + calleeEffect: Effect.Read, + returnValueKind: ValueKind.Mutable, + }), + ], + ]), + ], [ 'Array', addObject(DEFAULT_SHAPES, 'Array', [ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md new file mode 100644 index 0000000000..e491eb6c69 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md @@ -0,0 +1,49 @@ + +## Input + +```javascript +import {arrayPush} from 'shared-runtime'; + +function useFoo({a, b}) { + const obj = {a}; + arrayPush(Object.keys(obj), b); + return obj; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2, b: 3}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { arrayPush } from "shared-runtime"; + +function useFoo(t0) { + const $ = _c(2); + const { a, b } = t0; + let t1; + if ($[0] !== a) { + t1 = { a }; + $[0] = a; + $[1] = t1; + } else { + t1 = $[1]; + } + const obj = t1; + arrayPush(Object.keys(obj), b); + return obj; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: 2, b: 3 }], +}; + +``` + +### Eval output +(kind: ok) {"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts new file mode 100644 index 0000000000..9dbaac79c6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts @@ -0,0 +1,11 @@ +import {arrayPush} from 'shared-runtime'; + +function useFoo({a, b}) { + const obj = {a}; + arrayPush(Object.keys(obj), b); + return obj; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2, b: 3}], +}; From daab9e33b278d98db1eff5f20d72cea42ad0bbf4 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 19 Nov 2024 10:10:38 -0500 Subject: [PATCH 115/422] [compiler][rfc] enableTreatFunctionDepsAsConditional by default See https://github.com/facebook/react/issues/31551 for context We've rolled out internally with `enableTreatFunctionDepsAsConditional: false` and encountered no issues, but this is technically an unsound optimization as both flow and typescript have sources of unsoundness: - typing array accesses / other unknown keys as `TValue | undefined` (typescript's noUncheckedIndexedAccess) - explicit and inferred `any` - unsound typecasts Note that removing this optimization results in less granular dependencies for ~3% of files on a large Meta project ([link](https://www.internalfb.com/phabricator/paste/view/P1681742214)). --- .../src/HIR/Environment.ts | 15 +++++-- ...g-aliased-capture-aliased-mutate.expect.md | 4 +- .../bug-aliased-capture-mutate.expect.md | 4 +- ...g-function-member-expr-arguments.expect.md | 4 +- ...turing-function-member-expr-call.expect.md | 4 +- .../compiler/capturing-member-expr.expect.md | 4 +- ...ested-member-expr-in-nested-func.expect.md | 4 +- .../capturing-nested-member-expr.expect.md | 4 +- ...and-local-variables-with-default.expect.md | 4 +- ...ed-scope-declarations-and-locals.expect.md | 4 +- ...nction-expression-prototype-call.expect.md | 4 +- ...nvalid-useCallback-read-maybeRef.expect.md | 32 --------------- ...be-invalid-useMemo-read-maybeRef.expect.md | 32 --------------- ...nvalid-useCallback-read-maybeRef.expect.md | 38 ++++++++++++++++++ ...aybe-invalid-useCallback-read-maybeRef.ts} | 0 ...be-invalid-useMemo-read-maybeRef.expect.md | 40 +++++++++++++++++++ ...=> maybe-invalid-useMemo-read-maybeRef.ts} | 4 +- ...Callback-alias-property-load-dep.expect.md | 14 +++---- .../useCallback-alias-property-load-dep.ts | 6 +-- .../useCallback-infer-more-specific.expect.md | 4 +- ...r-function-uncond-access-hoisted.expect.md | 4 +- ...n-uncond-access-hoists-other-dep.expect.md | 4 +- ...function-uncond-access-local-var.expect.md | 4 +- ...uncond-optional-hoists-other-dep.expect.md | 4 +- ...function-uncond-access-local-var.expect.md | 4 +- ...er-nested-function-uncond-access.expect.md | 4 +- ...nfer-object-method-uncond-access.expect.md | 4 +- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../repro-invariant.expect.md | 4 +- ...l-dependency-on-context-variable.expect.md | 4 +- ...unction-uncond-optionals-hoisted.expect.md | 4 +- ...source-variables-nested-function.expect.md | 4 +- ...e-variables-nested-object-method.expect.md | 4 +- ...maybe-mutate-context-in-callback.expect.md | 4 +- ...context-in-callback-if-condition.expect.md | 4 +- ...Context-read-context-in-callback.expect.md | 4 +- .../useEffect-nested-lambdas.expect.md | 25 +++++------- 37 files changed, 165 insertions(+), 149 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useCallback-read-maybeRef.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useMemo-read-maybeRef.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useCallback-read-maybeRef.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{error.maybe-invalid-useCallback-read-maybeRef.ts => maybe-invalid-useCallback-read-maybeRef.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useMemo-read-maybeRef.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{error.maybe-invalid-useMemo-read-maybeRef.ts => maybe-invalid-useMemo-read-maybeRef.ts} (67%) 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 855bc039ab..cea202f2c9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -457,9 +457,10 @@ const EnvironmentConfigSchema = z.object({ throwUnknownException__testonly: z.boolean().default(false), /** - * Enables deps of a function epxression to be treated as conditional. This - * makes sure we don't load a dep when it's a property (to check if it has - * changed) and instead check the receiver. + * Enables deps of a function expression to be treated as conditional. This + * makes sure we don't hoist property loads from function expressions when we + * don't know that the property load source object is safe to access in the + * outer context * * This makes sure we don't end up throwing when the reciver is null. Consider * this code: @@ -475,8 +476,14 @@ const EnvironmentConfigSchema = z.object({ * * This does cause the memoization to now be coarse grained, which is * non-ideal. + * + * This is safe to toggle off for a codebase that has no sources of + * unsoundness. This includes: + * - typing array accesses / other unknown keys as TValue | undefined + * (typescript's noUncheckedIndexedAccess) + * - enabling and monitoring warnings for explicit and inferred `any` */ - enableTreatFunctionDepsAsConditional: z.boolean().default(false), + enableTreatFunctionDepsAsConditional: z.boolean().default(true), /** * When true, always act as though the dependencies of a memoized value diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md index d0ad9e2f9d..13adbc1c96 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md @@ -75,9 +75,9 @@ function useFoo(t0) { arrayPush(x, y); const y_alias = y; let t2; - if ($[3] !== y_alias.value) { + if ($[3] !== y_alias) { t2 = () => y_alias.value; - $[3] = y_alias.value; + $[3] = y_alias; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md index c35efe6a16..ae60114253 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md @@ -56,9 +56,9 @@ function useFoo(t0) { setPropertyByKey(obj, "arr", arr); const obj_alias = obj; let t2; - if ($[2] !== obj_alias.arr.length) { + if ($[2] !== obj_alias) { t2 = () => obj_alias.arr.length; - $[2] = obj_alias.arr.length; + $[2] = obj_alias; $[3] = t2; } else { t2 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-arguments.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-arguments.expect.md index 5a2c68b63c..d5e1f2e563 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-arguments.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-arguments.expect.md @@ -22,11 +22,11 @@ import { c as _c } from "react/compiler-runtime"; function Foo(props) { const $ = _c(2); let t0; - if ($[0] !== props.router.location) { + if ($[0] !== props.router) { t0 = (reason) => { log(props.router.location); }; - $[0] = props.router.location; + $[0] = props.router; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md index cab9c9a500..621ad98de7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md @@ -35,11 +35,11 @@ function component(t0) { } const poke = t1; let t2; - if ($[2] !== mutator.user) { + if ($[2] !== mutator) { t2 = () => { mutator.user.hide(); }; - $[2] = mutator.user; + $[2] = mutator; $[3] = t2; } else { t2 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-member-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-member-expr.expect.md index fcf42fac8c..c9d6b45656 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-member-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-member-expr.expect.md @@ -34,11 +34,11 @@ function component(a) { } const z = t0; let t1; - if ($[2] !== z.a) { + if ($[2] !== z) { t1 = function () { console.log(z.a); }; - $[2] = z.a; + $[2] = z; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-nested-member-expr-in-nested-func.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-nested-member-expr-in-nested-func.expect.md index e7d21da428..daf8d972cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-nested-member-expr-in-nested-func.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-nested-member-expr-in-nested-func.expect.md @@ -36,13 +36,13 @@ function component(a) { } const z = t0; let t1; - if ($[2] !== z.a.a) { + if ($[2] !== z) { t1 = function () { (function () { console.log(z.a.a); })(); }; - $[2] = z.a.a; + $[2] = z; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-nested-member-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-nested-member-expr.expect.md index f883378432..a5a6f7a321 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-nested-member-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-nested-member-expr.expect.md @@ -34,11 +34,11 @@ function component(a) { } const z = t0; let t1; - if ($[2] !== z.a.a) { + if ($[2] !== z) { t1 = function () { console.log(z.a.a); }; - $[2] = z.a.a; + $[2] = z; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.expect.md index 17dd0f8359..de029c0117 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.expect.md @@ -97,7 +97,7 @@ function Component(props) { } const urls = t5; let t6; - if ($[6] !== comments.length) { + if ($[6] !== comments) { t6 = (e) => { if (!comments.length) { return; @@ -105,7 +105,7 @@ function Component(props) { console.log(comments.length); }; - $[6] = comments.length; + $[6] = comments; $[7] = t6; } else { t6 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md index f69149aba4..d9c493ae98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md @@ -56,7 +56,7 @@ function Component(props) { const { media, comments, urls } = post; let t1; - if ($[2] !== comments.length) { + if ($[2] !== comments) { t1 = (e) => { if (!comments.length) { return; @@ -64,7 +64,7 @@ function Component(props) { console.log(comments.length); }; - $[2] = comments.length; + $[2] = comments; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call.expect.md index 5666876f00..2df5b90890 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call.expect.md @@ -23,11 +23,11 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.name) { + if ($[0] !== props) { t0 = function () { return
{props.name}
; }; - $[0] = props.name; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useCallback-read-maybeRef.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useCallback-read-maybeRef.expect.md deleted file mode 100644 index 87d4946b61..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useCallback-read-maybeRef.expect.md +++ /dev/null @@ -1,32 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function useHook(maybeRef) { - return useCallback(() => { - return [maybeRef.current]; - }, [maybeRef]); -} - -``` - - -## Error - -``` - 3 | - 4 | function useHook(maybeRef) { -> 5 | return useCallback(() => { - | ^^^^^^^ -> 6 | return [maybeRef.current]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -> 7 | }, [maybeRef]); - | ^^^^ 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:7) - 8 | } - 9 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useMemo-read-maybeRef.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useMemo-read-maybeRef.expect.md deleted file mode 100644 index ce0b751851..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useMemo-read-maybeRef.expect.md +++ /dev/null @@ -1,32 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useMemo} from 'react'; - -function useHook(maybeRef, shouldRead) { - return useMemo(() => { - return () => [maybeRef.current]; - }, [shouldRead, maybeRef]); -} - -``` - - -## Error - -``` - 3 | - 4 | function useHook(maybeRef, shouldRead) { -> 5 | return useMemo(() => { - | ^^^^^^^ -> 6 | return () => [maybeRef.current]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -> 7 | }, [shouldRead, maybeRef]); - | ^^^^ 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:7) - 8 | } - 9 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useCallback-read-maybeRef.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useCallback-read-maybeRef.expect.md new file mode 100644 index 0000000000..0ccfd1e43a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useCallback-read-maybeRef.expect.md @@ -0,0 +1,38 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function useHook(maybeRef) { + return useCallback(() => { + return [maybeRef.current]; + }, [maybeRef]); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees +import { useCallback } from "react"; + +function useHook(maybeRef) { + const $ = _c(2); + let t0; + if ($[0] !== maybeRef) { + t0 = () => [maybeRef.current]; + $[0] = maybeRef; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +``` + +### 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/preserve-memo-validation/error.maybe-invalid-useCallback-read-maybeRef.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useCallback-read-maybeRef.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useCallback-read-maybeRef.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useCallback-read-maybeRef.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useMemo-read-maybeRef.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useMemo-read-maybeRef.expect.md new file mode 100644 index 0000000000..a790a670aa --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useMemo-read-maybeRef.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useMemo} from 'react'; + +function useHook(maybeRef) { + return useMemo(() => { + return () => [maybeRef.current]; + }, [maybeRef]); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees +import { useMemo } from "react"; + +function useHook(maybeRef) { + const $ = _c(2); + let t0; + let t1; + if ($[0] !== maybeRef) { + t1 = () => [maybeRef.current]; + $[0] = maybeRef; + $[1] = t1; + } else { + t1 = $[1]; + } + t0 = t1; + return t0; +} + +``` + +### 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/preserve-memo-validation/error.maybe-invalid-useMemo-read-maybeRef.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useMemo-read-maybeRef.ts similarity index 67% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useMemo-read-maybeRef.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useMemo-read-maybeRef.ts index 1bf54a5aa5..36a7fd9a4c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-invalid-useMemo-read-maybeRef.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/maybe-invalid-useMemo-read-maybeRef.ts @@ -1,8 +1,8 @@ // @validatePreserveExistingMemoizationGuarantees import {useMemo} from 'react'; -function useHook(maybeRef, shouldRead) { +function useHook(maybeRef) { return useMemo(() => { return () => [maybeRef.current]; - }, [shouldRead, maybeRef]); + }, [maybeRef]); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-alias-property-load-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-alias-property-load-dep.expect.md index 5fceb7ef81..e06704c30c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-alias-property-load-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-alias-property-load-dep.expect.md @@ -9,13 +9,13 @@ import {sum} from 'shared-runtime'; function Component({propA, propB}) { const x = propB.x.y; return useCallback(() => { - return sum(propA.x, x); - }, [propA.x, x]); + return sum(propA, x); + }, [propA, x]); } export const FIXTURE_ENTRYPOINT = { fn: Component, - params: [{propA: {x: 2}, propB: {x: {y: 3}}}], + params: [{propA: 2, propB: {x: {y: 3}}}], }; ``` @@ -32,9 +32,9 @@ function Component(t0) { const { propA, propB } = t0; const x = propB.x.y; let t1; - if ($[0] !== propA.x || $[1] !== x) { - t1 = () => sum(propA.x, x); - $[0] = propA.x; + if ($[0] !== propA || $[1] !== x) { + t1 = () => sum(propA, x); + $[0] = propA; $[1] = x; $[2] = t1; } else { @@ -45,7 +45,7 @@ function Component(t0) { export const FIXTURE_ENTRYPOINT = { fn: Component, - params: [{ propA: { x: 2 }, propB: { x: { y: 3 } } }], + params: [{ propA: 2, propB: { x: { y: 3 } } }], }; ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-alias-property-load-dep.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-alias-property-load-dep.ts index be03a9ceaf..aa3481e269 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-alias-property-load-dep.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-alias-property-load-dep.ts @@ -5,11 +5,11 @@ import {sum} from 'shared-runtime'; function Component({propA, propB}) { const x = propB.x.y; return useCallback(() => { - return sum(propA.x, x); - }, [propA.x, x]); + return sum(propA, x); + }, [propA, x]); } export const FIXTURE_ENTRYPOINT = { fn: Component, - params: [{propA: {x: 2}, propB: {x: {y: 3}}}], + params: [{propA: 2, propB: {x: {y: 3}}}], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-more-specific.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-more-specific.expect.md index 7e57f29492..5c2192e35d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-more-specific.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-more-specific.expect.md @@ -37,9 +37,9 @@ import { useCallback } from "react"; function useHook(x) { const $ = _c(2); let t0; - if ($[0] !== x.y.z) { + if ($[0] !== x) { t0 = () => [x.y.z]; - $[0] = x.y.z; + $[0] = x; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoisted.expect.md index 1ddc7495bc..9ddead2734 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoisted.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoisted.expect.md @@ -29,9 +29,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b.c) { + if ($[0] !== a) { t1 = a.b.c} shouldInvokeFns={true} />; - $[0] = a.b.c; + $[0] = a; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md index d82956e4a0..eab9491c47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md @@ -51,12 +51,12 @@ function Foo(t0) { const fn = t1; useIdentity(null); let x; - if ($[2] !== a.b.c || $[3] !== cond) { + if ($[2] !== a || $[3] !== cond) { x = makeArray(); if (cond) { x.push(identity(a.b.c)); } - $[2] = a.b.c; + $[2] = a; $[3] = cond; $[4] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-local-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-local-var.expect.md index 741a30d7de..2536322cf7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-local-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-local-var.expect.md @@ -41,9 +41,9 @@ function useFoo(t0) { local = $[1]; } let t1; - if ($[2] !== local.b.c) { + if ($[2] !== local) { t1 = () => local.b.c; - $[2] = local.b.c; + $[2] = local; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md index c81e59ecea..7d8c1b32db 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md @@ -50,12 +50,12 @@ function Foo(t0) { const fn = t1; useIdentity(null); let arr; - if ($[2] !== a.b?.c.e || $[3] !== cond) { + if ($[2] !== a || $[3] !== cond) { arr = makeArray(); if (cond) { arr.push(identity(a.b?.c.e)); } - $[2] = a.b?.c.e; + $[2] = a; $[3] = cond; $[4] = arr; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access-local-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access-local-var.expect.md index ca65ce72bc..53d3d04531 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access-local-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access-local-var.expect.md @@ -41,9 +41,9 @@ function useFoo(t0) { local = $[1]; } let t1; - if ($[2] !== local.b.c) { + if ($[2] !== local) { t1 = () => [() => local.b.c]; - $[2] = local.b.c; + $[2] = local; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access.expect.md index 22b17977cb..e61e36d865 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access.expect.md @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(4); const { a } = t0; let t1; - if ($[0] !== a.b.c) { + if ($[0] !== a) { t1 = () => () => ({ value: a.b.c }); - $[0] = a.b.c; + $[0] = a; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-object-method-uncond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-object-method-uncond-access.expect.md index 7d75470550..f8a8af1fd4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-object-method-uncond-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-object-method-uncond-access.expect.md @@ -34,13 +34,13 @@ function useFoo(t0) { const $ = _c(4); const { a } = t0; let t1; - if ($[0] !== a.b.c) { + if ($[0] !== a) { t1 = { fn() { return identity(a.b.c); }, }; - $[0] = a.b.c; + $[0] = a; $[1] = t1; } else { t1 = $[1]; 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 ed56ff0681..883d4dc8d6 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?.c.d?.e) { + if ($[0] !== a) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b?.c.d?.e; + $[0] = a; $[1] = t1; } else { t1 = $[1]; 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 index 73df2b615b..cdb9d67e36 100644 --- 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 @@ -29,9 +29,9 @@ function Foo(t0) { const $ = _c(5); const { data } = t0; let t1; - if ($[0] !== data.a.d) { + if ($[0] !== data.a) { t1 = () => data.a.d; - $[0] = data.a.d; + $[0] = data.a; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md index 23cc7ee846..a8fa9105e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md @@ -51,7 +51,7 @@ import { identity } from "shared-runtime"; function Component(props) { const $ = _c(4); let x; - if ($[0] !== props.cond) { + if ($[0] !== props) { const f = () => { if (props.cond) { x = 1; @@ -62,7 +62,7 @@ function Component(props) { const f2 = identity(f); f2(); - $[0] = props.cond; + $[0] = props; $[1] = x; } else { x = $[1]; 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 bb99a5d90f..1c65af0fe6 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?.c.d?.e) { + if ($[0] !== a) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b?.c.d?.e; + $[0] = a; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-function.expect.md index 3287997cf7..1957b3b0b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-function.expect.md @@ -42,7 +42,7 @@ const t0 = "module_t0"; const c_0 = "module_c_0"; function useFoo(props) { const $0 = _c(4); - const c_00 = $0[0] !== props.value; + const c_00 = $0[0] !== props; let t1; if (c_00) { t1 = () => { @@ -57,7 +57,7 @@ function useFoo(props) { }; return b; }; - $0[0] = props.value; + $0[0] = props; $0[1] = t1; } else { t1 = $0[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-object-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-object-method.expect.md index d0f3d5dcfe..e406f3a7d7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-object-method.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-object-method.expect.md @@ -43,7 +43,7 @@ const t0 = "module_t0"; const c_0 = "module_c_0"; function useFoo(props) { const $0 = _c(2); - const c_00 = $0[0] !== props.value; + const c_00 = $0[0] !== props; let t1; if (c_00) { const a = { @@ -61,7 +61,7 @@ function useFoo(props) { }; t1 = a.foo().bar(); - $0[0] = props.value; + $0[0] = props; $0[1] = t1; } else { t1 = $0[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-maybe-mutate-context-in-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-maybe-mutate-context-in-callback.expect.md index 0525a0f7cd..1170316958 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-maybe-mutate-context-in-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-maybe-mutate-context-in-callback.expect.md @@ -41,11 +41,11 @@ function Component(props) { const $ = _c(5); const Foo = useContext(FooContext); let t0; - if ($[0] !== Foo.current) { + if ($[0] !== Foo) { t0 = () => { mutate(Foo.current); }; - $[0] = Foo.current; + $[0] = Foo; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-read-context-in-callback-if-condition.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-read-context-in-callback-if-condition.expect.md index 1d09cbb359..bd45c13948 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-read-context-in-callback-if-condition.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-read-context-in-callback-if-condition.expect.md @@ -42,7 +42,7 @@ function Component(props) { const $ = _c(6); const foo = useContext(FooContext); let t0; - if ($[0] !== foo.current) { + if ($[0] !== foo) { t0 = () => { if (foo.current) { return {}; @@ -50,7 +50,7 @@ function Component(props) { return null; } }; - $[0] = foo.current; + $[0] = foo; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-read-context-in-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-read-context-in-callback.expect.md index e805b7f400..09dbbc230f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-read-context-in-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useContext-read-context-in-callback.expect.md @@ -34,11 +34,11 @@ function Component(props) { const $ = _c(5); const foo = useContext(FooContext); let t0; - if ($[0] !== foo.current) { + if ($[0] !== foo) { t0 = () => { console.log(foo.current); }; - $[0] = foo.current; + $[0] = foo; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md index c3e115fa0d..0cce42e97a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md @@ -35,7 +35,7 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enableTransitivelyFreezeFunctionExpressions:false function Component(props) { - const $ = _c(9); + const $ = _c(7); const item = useMutable(props.itemId); const dispatch = useDispatch(); useFreeze(dispatch); @@ -51,7 +51,8 @@ function Component(props) { } const exit = t0; let t1; - if ($[2] !== exit || $[3] !== item.value) { + let t2; + if ($[2] !== exit || $[3] !== item) { t1 = () => { const cleanup = GlobalEventEmitter.addListener("onInput", () => { if (item.value) { @@ -60,30 +61,24 @@ function Component(props) { }); return () => cleanup.remove(); }; + t2 = [exit, item]; $[2] = exit; - $[3] = item.value; + $[3] = item; $[4] = t1; + $[5] = t2; } else { t1 = $[4]; - } - let t2; - if ($[5] !== exit || $[6] !== item) { - t2 = [exit, item]; - $[5] = exit; - $[6] = item; - $[7] = t2; - } else { - t2 = $[7]; + t2 = $[5]; } useEffect(t1, t2); maybeMutate(item); let t3; - if ($[8] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t3 =
; - $[8] = t3; + $[6] = t3; } else { - t3 = $[8]; + t3 = $[6]; } return t3; } From 138f74708b9bf05484e550a2d399be758ad5a5bd Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 2 Dec 2024 16:17:57 -0500 Subject: [PATCH 116/422] [compiler] Add meta internal option for useMemoCache import Adds `target: 'donotuse_meta_internal'`, which inserts useMemoCache imports directly from `react`. Note that this is only valid for Meta bundles, as others do not [re-export the `c` function](https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/packages/react/index.fb.js#L68-L70). ```js // target=donotuse_meta_internal import {c as _c} from 'react'; // target=19 import {c as _c} from 'react/compiler-runtime'; // target=17,18 import {c as _c} from 'react-compiler-runtime'; ``` Meta is a bit special in that react runtime and compiler are guaranteed to be up-to-date and compatible. It also has its own bundling and module resolution logic, which makes importing from `react/compiler-runtime` tricky. I'm also fine with implementing the alternative which adds an internal stub for `react-compiler-runtime` and [bundles](https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/scripts/rollup/bundles.js#L120) the runtime for internal builds. --- .../src/Entrypoint/Options.ts | 14 +++++- .../src/Entrypoint/Program.ts | 7 ++- .../target-flag-meta-internal.expect.md | 43 +++++++++++++++++++ .../compiler/target-flag-meta-internal.js | 11 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 10bcebe44e..2c2bce2db3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -121,7 +121,19 @@ export type PluginOptions = { target: CompilerReactTarget; }; -const CompilerReactTargetSchema = z.enum(['17', '18', '19']); +const CompilerReactTargetSchema = z.enum([ + '17', + '18', + '19', + /** + * Used exclusively for Meta apps which are guaranteed to have compatible + * react runtime and compiler versions. Note that only the FB-internal bundles + * re-export useMemoCache (see + * https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/packages/react/index.fb.js#L68-L70), + * so this option is invalid / creates runtime errors for open-source users. + */ + 'donotuse_meta_internal', +]); export type CompilerReactTarget = z.infer; const CompilationModeSchema = z.enum([ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index f5dbb324bf..00f32f956a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -1125,7 +1125,8 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel( type ReactCompilerRuntimeModule = | 'react/compiler-runtime' // from react namespace - | 'react-compiler-runtime'; // npm package + | 'react-compiler-runtime' // npm package + | 'react'; function getReactCompilerRuntimeModule( opts: PluginOptions, ): ReactCompilerRuntimeModule { @@ -1140,6 +1141,10 @@ function getReactCompilerRuntimeModule( moduleName = 'react/compiler-runtime'; break; } + case 'donotuse_meta_internal': { + moduleName = 'react'; + break; + } default: CompilerError.invariant(moduleName != null, { reason: 'Expected target to already be validated', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md new file mode 100644 index 0000000000..acf34a474c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md @@ -0,0 +1,43 @@ + +## Input + +```javascript +// @target="donotuse_meta_internal" + +function Component() { + return
Hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react"; // @target="donotuse_meta_internal" + +function Component() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
Hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js new file mode 100644 index 0000000000..02f71b841c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js @@ -0,0 +1,11 @@ +// @target="donotuse_meta_internal" + +function Component() { + return
Hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index bb51ece12f..36b3e92f96 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -504,6 +504,7 @@ const skipFilter = new Set([ // Depends on external functions 'idx-method-no-outlining-wildcard', 'idx-method-no-outlining', + 'target-flag-meta-internal', // needs to be executed as a module 'meta-property', From c07531f07123bb250f312e8b701336d812bb24f5 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 2 Dec 2024 16:17:57 -0500 Subject: [PATCH 117/422] [compiler] Add meta internal option for useMemoCache import Adds `target: 'donotuse_meta_internal'`, which inserts useMemoCache imports directly from `react`. Note that this is only valid for Meta bundles, as others do not [re-export the `c` function](https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/packages/react/index.fb.js#L68-L70). ```js // target=donotuse_meta_internal import {c as _c} from 'react'; // target=19 import {c as _c} from 'react/compiler-runtime'; // target=17,18 import {c as _c} from 'react-compiler-runtime'; ``` Meta is a bit special in that react runtime and compiler are guaranteed to be up-to-date and compatible. It also has its own bundling and module resolution logic, which makes importing from `react/compiler-runtime` tricky. I'm also fine with implementing the alternative which adds an internal stub for `react-compiler-runtime` and [bundles](https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/scripts/rollup/bundles.js#L120) the runtime for internal builds. --- .../src/Entrypoint/Options.ts | 17 +++++++- .../src/Entrypoint/Program.ts | 35 ++++++--------- .../target-flag-meta-internal.expect.md | 43 +++++++++++++++++++ .../compiler/target-flag-meta-internal.js | 11 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + compiler/packages/snap/src/compiler.ts | 14 ++++-- 6 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 10bcebe44e..72ed9e7c86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -121,7 +121,22 @@ export type PluginOptions = { target: CompilerReactTarget; }; -const CompilerReactTargetSchema = z.enum(['17', '18', '19']); +const CompilerReactTargetSchema = z.union([ + z.literal('17'), + z.literal('18'), + z.literal('19'), + /** + * Used exclusively for Meta apps which are guaranteed to have compatible + * react runtime and compiler versions. Note that only the FB-internal bundles + * re-export useMemoCache (see + * https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/packages/react/index.fb.js#L68-L70), + * so this option is invalid / creates runtime errors for open-source users. + */ + z.object({ + kind: z.literal('donotuse_meta_internal'), + runtimeModule: z.string().default('react'), + }), +]); export type CompilerReactTarget = z.infer; const CompilationModeSchema = z.enum([ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index f5dbb324bf..a6e09a1d06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -1123,30 +1123,23 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel( return errors.details.length > 0 ? errors : null; } -type ReactCompilerRuntimeModule = - | 'react/compiler-runtime' // from react namespace - | 'react-compiler-runtime'; // npm package -function getReactCompilerRuntimeModule( - opts: PluginOptions, -): ReactCompilerRuntimeModule { - let moduleName: ReactCompilerRuntimeModule | null = null; - switch (opts.target) { - case '17': - case '18': { - moduleName = 'react-compiler-runtime'; - break; - } - case '19': { - moduleName = 'react/compiler-runtime'; - break; - } - default: - CompilerError.invariant(moduleName != null, { +function getReactCompilerRuntimeModule(opts: PluginOptions): string { + if (opts.target === '19') { + return 'react/compiler-runtime'; // from react namespace + } else if (opts.target === '17' || opts.target === '18') { + return 'react-compiler-runtime'; // npm package + } else { + CompilerError.invariant( + opts.target != null && + opts.target.kind === 'donotuse_meta_internal' && + typeof opts.target.runtimeModule === 'string', + { reason: 'Expected target to already be validated', description: null, loc: null, suggestions: null, - }); + }, + ); + return opts.target.runtimeModule; } - return moduleName; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md new file mode 100644 index 0000000000..acf34a474c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md @@ -0,0 +1,43 @@ + +## Input + +```javascript +// @target="donotuse_meta_internal" + +function Component() { + return
Hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react"; // @target="donotuse_meta_internal" + +function Component() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
Hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js new file mode 100644 index 0000000000..02f71b841c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js @@ -0,0 +1,11 @@ +// @target="donotuse_meta_internal" + +function Component() { + return
Hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index bb51ece12f..36b3e92f96 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -504,6 +504,7 @@ const skipFilter = new Set([ // Depends on external functions 'idx-method-no-outlining-wildcard', 'idx-method-no-outlining', + 'target-flag-meta-internal', // needs to be executed as a module 'meta-property', diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 95af40d62a..68acdbc790 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -18,6 +18,7 @@ import type { LoggerEvent, PanicThresholdOptions, PluginOptions, + CompilerReactTarget, } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; import type { @@ -55,7 +56,7 @@ function makePluginOptions( let validatePreserveExistingMemoizationGuarantees = false; let customMacros: null | Array = null; let validateBlocklistedImports = null; - let target = '19' as const; + let target: CompilerReactTarget = '19'; if (firstLine.indexOf('@compilationMode(annotation)') !== -1) { assert( @@ -81,8 +82,15 @@ function makePluginOptions( const targetMatch = /@target="([^"]+)"/.exec(firstLine); if (targetMatch) { - // @ts-ignore - target = targetMatch[1]; + if (targetMatch[1] === 'donotuse_meta_internal') { + target = { + kind: targetMatch[1], + runtimeModule: 'react', + }; + } else { + // @ts-ignore + target = targetMatch[1]; + } } if (firstLine.includes('@panicThreshold(none)')) { From 829c1cd54eae8d7e2b93ba1054206d72158f3f24 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 13 Dec 2024 17:15:29 -0500 Subject: [PATCH 118/422] [compiler] Repro for aliased captures within inner function expressions see fixture --- ...-func-maybealias-captured-mutate.expect.md | 129 ++++++++++++++++++ ...pturing-func-maybealias-captured-mutate.ts | 48 +++++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 178 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-capturing-func-maybealias-captured-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-capturing-func-maybealias-captured-mutate.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-capturing-func-maybealias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-capturing-func-maybealias-captured-mutate.expect.md new file mode 100644 index 0000000000..b8c7f8d422 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-capturing-func-maybealias-captured-mutate.expect.md @@ -0,0 +1,129 @@ + +## Input + +```javascript +import {makeArray, mutate} from 'shared-runtime'; + +/** + * Bug repro: + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * {"bar":4,"x":{"foo":3,"wat0":"joe"}} + * {"bar":5,"x":{"foo":3,"wat0":"joe"}} + * Forget: + * (kind: ok) + * {"bar":4,"x":{"foo":3,"wat0":"joe"}} + * {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}} + * + * Fork of `capturing-func-alias-captured-mutate`, but instead of directly + * aliasing `y` via `[y]`, we make an opaque call. + * + * Note that the bug here is that we don't infer that `a = makeArray(y)` + * potentially captures a context variable into a local variable. As a result, + * we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're + * currently inferring that this lambda captures `y` (for a potential later + * mutation) and simply reads `x`. + * + * Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not + * used when we analyze CallExpressions. + */ +function Component({foo, bar}: {foo: number; bar: number}) { + let x = {foo}; + let y: {bar: number; x?: {foo: number}} = {bar}; + const f0 = function () { + let a = makeArray(y); // a = [y] + let b = x; + // this writes y.x = x + a[0].x = b; + }; + f0(); + 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"; +import { makeArray, mutate } from "shared-runtime"; + +/** + * Bug repro: + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * {"bar":4,"x":{"foo":3,"wat0":"joe"}} + * {"bar":5,"x":{"foo":3,"wat0":"joe"}} + * Forget: + * (kind: ok) + * {"bar":4,"x":{"foo":3,"wat0":"joe"}} + * {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}} + * + * Fork of `capturing-func-alias-captured-mutate`, but instead of directly + * aliasing `y` via `[y]`, we make an opaque call. + * + * Note that the bug here is that we don't infer that `a = makeArray(y)` + * potentially captures a context variable into a local variable. As a result, + * we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're + * currently inferring that this lambda captures `y` (for a potential later + * mutation) and simply reads `x`. + * + * Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not + * used when we analyze CallExpressions. + */ +function Component(t0) { + const $ = _c(5); + const { foo, bar } = t0; + let t1; + if ($[0] !== foo) { + t1 = { foo }; + $[0] = foo; + $[1] = t1; + } else { + t1 = $[1]; + } + const x = t1; + let y; + if ($[2] !== bar || $[3] !== x) { + y = { bar }; + const f0 = function () { + const a = makeArray(y); + const b = x; + + a[0].x = b; + }; + + f0(); + mutate(y.x); + $[2] = bar; + $[3] = x; + $[4] = y; + } else { + y = $[4]; + } + 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 diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-capturing-func-maybealias-captured-mutate.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-capturing-func-maybealias-captured-mutate.ts new file mode 100644 index 0000000000..ca7076fda4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-capturing-func-maybealias-captured-mutate.ts @@ -0,0 +1,48 @@ +import {makeArray, mutate} from 'shared-runtime'; + +/** + * Bug repro: + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * {"bar":4,"x":{"foo":3,"wat0":"joe"}} + * {"bar":5,"x":{"foo":3,"wat0":"joe"}} + * Forget: + * (kind: ok) + * {"bar":4,"x":{"foo":3,"wat0":"joe"}} + * {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}} + * + * Fork of `capturing-func-alias-captured-mutate`, but instead of directly + * aliasing `y` via `[y]`, we make an opaque call. + * + * Note that the bug here is that we don't infer that `a = makeArray(y)` + * potentially captures a context variable into a local variable. As a result, + * we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're + * currently inferring that this lambda captures `y` (for a potential later + * mutation) and simply reads `x`. + * + * Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not + * used when we analyze CallExpressions. + */ +function Component({foo, bar}: {foo: number; bar: number}) { + let x = {foo}; + let y: {bar: number; x?: {foo: number}} = {bar}; + const f0 = function () { + let a = makeArray(y); // a = [y] + let b = x; + // this writes y.x = x + a[0].x = b; + }; + f0(); + 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/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 36b3e92f96..3610707396 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -479,6 +479,7 @@ const skipFilter = new Set([ // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', + `bug-capturing-func-maybealias-captured-mutate`, 'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr', 'bug-invalid-hoisting-functionexpr', 'bug-aliased-capture-aliased-mutate', From f8050e31c2ba4c382e0a7f1428b26991c8fa1c33 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 13 Dec 2024 19:35:46 -0500 Subject: [PATCH 119/422] [compiler][be] Playground now compiles entire program Compiler playground now runs entire program through `babel-plugin-react-compiler` instead of a custom pipeline, which previously duplicated function inference logic from `Program.ts`. In addition, the playground output reflects the tranformed file (instead of a "virtual file" of manually concatenated functions). This helps with the following: - Reduce potential discrepencies between playground and babel plugin behavior. See attached fixture output for an example where we previously diverged. - Let playground users see compiler-inserted imports (e.g. `_c` or `useFire`) This also helps us repurpose playground into a more generic tool for compiler-users instead of just compiler engineers. - imports and other functions are preserved. This is important as we now differentiate between imports and globals in many cases, - playground now shows other program-changing behavior like position of outlined functions and hoisted declarations - emitted compiled functions do not need synthetic names --- .../page.spec.ts/01-user-output.txt | 3 +- .../page.spec.ts/02-default-output.txt | 3 +- .../module-scope-use-memo-output.txt | 4 +- .../module-scope-use-no-memo-output.txt | 3 +- ...cope-does-not-beat-module-scope-output.txt | 5 + .../page.spec.ts/use-memo-output.txt | 5 +- .../page.spec.ts/use-no-memo-output.txt | 8 +- .../playground/__tests__/e2e/page.spec.ts | 2 +- .../components/Editor/EditorImpl.tsx | 259 +++++------------- .../playground/components/Editor/Output.tsx | 56 ++-- compiler/apps/playground/package.json | 1 + compiler/apps/playground/yarn.lock | 20 ++ .../src/Babel/BabelPlugin.ts | 5 +- .../src/Entrypoint/Options.ts | 2 + .../src/Entrypoint/Pipeline.ts | 139 +++++----- .../src/HIR/Environment.ts | 9 +- .../src/Utils/logger.ts | 4 - ...ule-scope-usememo-function-scope.expect.md | 29 ++ ...emo-module-scope-usememo-function-scope.js | 7 + .../babel-plugin-react-compiler/src/index.ts | 2 - compiler/packages/snap/src/runner-worker.ts | 6 +- 21 files changed, 248 insertions(+), 324 deletions(-) create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt index ba680bbb57..1600f35107 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt @@ -1,4 +1,5 @@ -function TestComponent(t0) { +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt index 2cbd09bba6..1d59a120f9 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt @@ -1,4 +1,5 @@ -function MyApp() { +import { c as _c } from "react/compiler-runtime"; +export default function MyApp() { const $ = _c(1); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt index ba680bbb57..638a2bcd22 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt @@ -1,4 +1,6 @@ -function TestComponent(t0) { +"use memo"; +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt index 2c69ddc1d6..ebd2d2b046 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt @@ -1,3 +1,4 @@ -function TestComponent({ x }) { +"use no memo"; +export default function TestComponent({ x }) { return ; } diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt new file mode 100644 index 0000000000..325e6972e1 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt @@ -0,0 +1,5 @@ +"use no memo"; +function TestComponent({ x }) { + "use memo"; + return ; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt index 804bacab97..de6dd52680 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt @@ -1,3 +1,4 @@ +import { c as _c } from "react/compiler-runtime"; function TestComponent(t0) { "use memo"; const $ = _c(2); @@ -12,7 +13,7 @@ function TestComponent(t0) { } return t1; } -function anonymous_1(t0) { +const TestComponent2 = (t0) => { "use memo"; const $ = _c(2); const { x } = t0; @@ -25,4 +26,4 @@ function anonymous_1(t0) { t1 = $[1]; } return t1; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt index 5fb66309fc..02c1367622 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt @@ -1,8 +1,8 @@ -function anonymous_1() { +const TestComponent = function () { "use no memo"; return ; -} -function anonymous_3({ x }) { +}; +const TestComponent2 = ({ x }) => { "use no memo"; return ; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 846e6227bd..254002c5ce 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -55,7 +55,7 @@ const TestComponent2 = ({ x }) => { };`, }, { - name: 'function-scope-beats-module-scope', + name: 'todo-function-scope-does-not-beat-module-scope', input: ` 'use no memo'; function TestComponent({ x }) { diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 82a40272bd..002bea052e 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,23 +5,23 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse} from '@babel/parser'; +import {parse as babelParse, ParseResult} from '@babel/parser'; +import transformStripFlowTypes from "@babel/plugin-transform-flow-strip-types"; import * as HermesParser from 'hermes-parser'; -import traverse, {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import { +import BabelPluginReactCompiler, { CompilerError, CompilerErrorDetail, Effect, ErrorSeverity, parseConfigPragmaForTests, ValueKind, - runPlayground, type Hook, - findDirectiveDisablingMemoization, - findDirectiveEnablingMemoization, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, } from 'babel-plugin-react-compiler/src'; -import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment'; import clsx from 'clsx'; import invariant from 'invariant'; import {useSnackbar} from 'notistack'; @@ -44,27 +44,9 @@ import { } from './Output'; import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; +import { BabelFileResult, transformFromAstSync } from '@babel/core'; -type FunctionLike = - | NodePath - | NodePath - | NodePath; -enum MemoizeDirectiveState { - Enabled = 'Enabled', - Disabled = 'Disabled', - Undefined = 'Undefined', -} - -const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Undefined, -]); - -const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Disabled, -]); -function parseInput(input: string, language: 'flow' | 'typescript'): any { +function parseInput(input: string, language: 'flow' | 'typescript'): ParseResult { // Extract the first line to quickly check for custom test directives if (language === 'flow') { return HermesParser.parse(input, { @@ -77,95 +59,70 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { return babelParse(input, { plugins: ['typescript', 'jsx'], sourceType: 'module', - }); + }) as ParseResult; } } -function parseFunctions( +function invokeCompiler( source: string, language: 'flow' | 'typescript', -): Array<{ - compilationEnabled: boolean; - fn: FunctionLike; -}> { - const items: Array<{ - compilationEnabled: boolean; - fn: FunctionLike; - }> = []; + environment: EnvironmentConfig, + logIR: (pipelineValue: CompilerPipelineValue) => void, +): { ast: t.File, code: string, sourceMaps: BabelFileResult['map']} { + const opts: PluginOptions = parsePluginOptions({ + logger: { + debugLogIRs: logIR, + logEvent: () => {} + }, + environment, + compilationMode: 'all' + }); + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [ + [BabelPluginReactCompiler, opts], + ], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error("Expected successful compilation"); + } try { - const ast = parseInput(source, language); - traverse(ast, { - FunctionDeclaration(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - ArrowFunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - FunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - }); + babelParse(result.code, { + plugins: ['jsx'], + sourceType: 'module', + }) as ParseResult; } catch (e) { - console.error(e); - CompilerError.throwInvalidJS({ - reason: String(e), - description: null, - loc: null, - suggestions: null, - }); - } - - return items; -} - -function shouldCompile(fn: FunctionLike): boolean { - const {body} = fn.node; - if (t.isBlockStatement(body)) { - const selfCheck = checkExplicitMemoizeDirectives(body.directives); - if (selfCheck === MemoizeDirectiveState.Enabled) return true; - if (selfCheck === MemoizeDirectiveState.Disabled) return false; - - const parentWithDirective = fn.findParent(parentPath => { - if (parentPath.isBlockStatement() || parentPath.isProgram()) { - const directiveCheck = checkExplicitMemoizeDirectives( - parentPath.node.directives, - ); - return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck); + console.warn("Retrying after parse error (likely due to flow annotations): ", e) + result = transformFromAstSync(ast, source, + { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [transformStripFlowTypes], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, } - return false; - }); - - if (!parentWithDirective) return true; - const parentDirectiveCheck = checkExplicitMemoizeDirectives( - (parentWithDirective.node as t.Program | t.BlockStatement).directives, ); - return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); } - return false; -} - -function checkExplicitMemoizeDirectives( - directives: Array, -): MemoizeDirectiveState { - if (findDirectiveEnablingMemoization(directives).length) { - return MemoizeDirectiveState.Enabled; + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error("Expected successful compilation"); } - if (findDirectiveDisablingMemoization(directives).length) { - return MemoizeDirectiveState.Disabled; - } - return MemoizeDirectiveState.Undefined; + return { + ast: result.ast, + code: result.code, + sourceMaps: result.map + }; } const COMMON_HOOKS: Array<[string, Hook]> = [ @@ -216,37 +173,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ ], ]; -function isHookName(s: string): boolean { - return /^use[A-Z0-9]/.test(s); -} - -function getReactFunctionType(id: t.Identifier | null): ReactFunctionType { - if (id != null) { - if (isHookName(id.name)) { - return 'Hook'; - } - - const isPascalCaseNameSpace = /^[A-Z].*/; - if (isPascalCaseNameSpace.test(id.name)) { - return 'Component'; - } - } - return 'Other'; -} - -function getFunctionIdentifier( - fn: - | NodePath - | NodePath - | NodePath, -): t.Identifier | null { - if (fn.isArrowFunctionExpression()) { - return null; - } - const id = fn.get('id'); - return Array.isArray(id) === false && id.isIdentifier() ? id.node : null; -} - function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map>(); const error = new CompilerError(); @@ -272,63 +198,21 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { return t.identifier(`anonymous_${count++}`); } }; + let transformOutput; try { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); const config = parseConfigPragmaForTests(pragma); - const parsedFunctions = parseFunctions(source, language); - for (const func of parsedFunctions) { - const id = withIdentifier(getFunctionIdentifier(func.fn)); - const fnName = id.name; - if (!func.compilationEnabled) { - upsert({ - kind: 'ast', - fnName, - name: 'CodeGen', - value: { - type: 'FunctionDeclaration', - id: - func.fn.isArrowFunctionExpression() || - func.fn.isFunctionExpression() - ? withIdentifier(null) - : func.fn.node.id, - async: func.fn.node.async, - generator: !!func.fn.node.generator, - body: func.fn.node.body as t.BlockStatement, - params: func.fn.node.params, - }, - }); - continue; - } - for (const result of runPlayground( - func.fn, - { - ...config, - customHooks: new Map([...COMMON_HOOKS]), - }, - getReactFunctionType(id), - )) { + + transformOutput = invokeCompiler(source, language, {...config, customHooks: new Map([...COMMON_HOOKS])}, (result) => { switch (result.kind) { case 'ast': { - upsert({ - kind: 'ast', - fnName, - name: result.name, - value: { - type: 'FunctionDeclaration', - id: withIdentifier(result.value.id), - async: result.value.async, - generator: result.value.generator, - body: result.value.body, - params: result.value.params, - }, - }); break; } case 'hir': { upsert({ kind: 'hir', - fnName, + fnName: result.value.id, name: result.name, value: printFunctionWithOutlined(result.value), }); @@ -337,7 +221,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'reactive': { upsert({ kind: 'reactive', - fnName, + fnName: result.value.id, name: result.name, value: printReactiveFunctionWithOutlined(result.value), }); @@ -346,7 +230,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'debug': { upsert({ kind: 'debug', - fnName, + fnName: null, name: result.name, value: result.value, }); @@ -357,8 +241,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { throw new Error(`Unhandled result ${result}`); } } - } - } + }); } catch (err) { /** * error might be an invariant violation or other runtime error @@ -385,7 +268,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { if (error.hasErrors()) { return [{kind: 'err', results, error: error}, language]; } - return [{kind: 'ok', results}, language]; + return [{kind: 'ok', results, transformOutput}, language]; } export default function Editor(): JSX.Element { @@ -405,7 +288,7 @@ export default function Editor(): JSX.Element { } catch (e) { invariant(e instanceof Error, 'Only Error may be caught.'); enqueueSnackbar(e.message, { - variant: 'message', + variant: 'warning', ...createMessage( 'Bad URL - fell back to the default Playground.', MessageLevel.Info, diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 97a63400d2..7dab9587ad 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import generate from '@babel/generator'; import * as t from '@babel/types'; import { CodeIcon, @@ -21,17 +20,12 @@ import {memo, ReactNode, useEffect, useState} from 'react'; import {type Store} from '../../lib/stores'; import TabbedWindow from '../TabbedWindow'; import {monacoOptions} from './monacoOptions'; +import { BabelFileResult } from '@babel/core'; const MemoizedOutput = memo(Output); export default MemoizedOutput; export type PrintedCompilerPipelineValue = - | { - kind: 'ast'; - name: string; - fnName: string | null; - value: t.FunctionDeclaration; - } | { kind: 'hir'; name: string; @@ -42,7 +36,11 @@ export type PrintedCompilerPipelineValue = | {kind: 'debug'; name: string; fnName: string | null; value: string}; export type CompilerOutput = - | {kind: 'ok'; results: Map>} + | {kind: 'ok'; transformOutput: { + ast: t.File + code: string + sourceMaps: BabelFileResult['map'] + }, results: Map>} | { kind: 'err'; results: Map>; @@ -61,7 +59,6 @@ async function tabify( const tabs = new Map(); const reorderedTabs = new Map(); const concattedResults = new Map(); - let topLevelFnDecls: Array = []; // Concat all top level function declaration results into a single tab for each pass for (const [passName, results] of compilerOutput.results) { for (const result of results) { @@ -87,9 +84,6 @@ async function tabify( } break; } - case 'ast': - topLevelFnDecls.push(result.value); - break; case 'debug': { concattedResults.set(passName, result.value); break; @@ -114,13 +108,16 @@ async function tabify( lastPassOutput = text; } // Ensure that JS and the JS source map come first - if (topLevelFnDecls.length > 0) { - /** - * Make a synthetic Program so we can have a single AST with all the top level - * FunctionDeclarations - */ - const ast = t.program(topLevelFnDecls); - const {code, sourceMapUrl} = await codegen(ast, source); + if (compilerOutput.kind === 'ok') { + const sourceMapUrl = getSourceMapUrl( + compilerOutput.transformOutput.code, + JSON.stringify(compilerOutput.transformOutput.sourceMaps), + ); + const code = await prettier.format(compilerOutput.transformOutput.code, { + semi: true, + parser: 'babel', + plugins: [parserBabel, prettierPluginEstree], + }); reorderedTabs.set( 'JS', { - const generated = generate( - ast, - {sourceMaps: true, sourceFileName: 'input.js'}, - source, - ); - const sourceMapUrl = getSourceMapUrl( - generated.code, - JSON.stringify(generated.map), - ); - const codegenOutput = await prettier.format(generated.code, { - semi: true, - parser: 'babel', - plugins: [parserBabel, prettierPluginEstree], - }); - return {code: codegenOutput, sourceMapUrl}; -} - function utf16ToUTF8(s: string): string { return unescape(encodeURIComponent(s)); } diff --git a/compiler/apps/playground/package.json b/compiler/apps/playground/package.json index c3dd825f1a..e249a3f30e 100644 --- a/compiler/apps/playground/package.json +++ b/compiler/apps/playground/package.json @@ -42,6 +42,7 @@ "react-dom": "19.0.0-rc-77b637d6-20241016" }, "devDependencies": { + "@babel/plugin-transform-flow-strip-types": "^7.25.9", "@types/node": "18.11.9", "@types/react": "npm:types-react@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", diff --git a/compiler/apps/playground/yarn.lock b/compiler/apps/playground/yarn.lock index dc5362548a..3e12ad0a31 100644 --- a/compiler/apps/playground/yarn.lock +++ b/compiler/apps/playground/yarn.lock @@ -128,6 +128,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== +"@babel/helper-plugin-utils@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== + "@babel/helper-replace-supers@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9" @@ -193,6 +198,13 @@ dependencies: "@babel/types" "^7.25.6" +"@babel/plugin-syntax-flow@^7.25.9": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz#96507595c21b45fccfc2bc758d5c45452e6164fa" + integrity sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-jsx@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" @@ -214,6 +226,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-flow-strip-types@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.9.tgz#85879b42a8f5948fd6317069978e98f23ef8aec1" + integrity sha512-/VVukELzPDdci7UUsWQaSkhgnjIWXnIyRpM02ldxaVoFK96c41So8JcKT3m0gYjyv7j5FNPGS5vfELrWalkbDA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-flow" "^7.25.9" + "@babel/plugin-transform-modules-commonjs@^7.18.9", "@babel/plugin-transform-modules-commonjs@^7.24.7": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c" diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts index 401cbd4bdf..c648c66043 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -39,7 +39,10 @@ export default function BabelPluginReactCompiler( ) { opts = injectReanimatedFlag(opts); } - if (isDev) { + if ( + opts.environment.enableResetCacheOnSourceFileChanges !== false && + isDev + ) { opts = { ...opts, environment: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 72ed9e7c86..fb951d25c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -15,6 +15,7 @@ import { } from '../HIR/Environment'; import {hasOwnProperty} from '../Utils/utils'; import {fromZodError} from 'zod-validation-error'; +import {CompilerPipelineValue} from './Pipeline'; const PanicThresholdOptionsSchema = z.enum([ /* @@ -209,6 +210,7 @@ export type LoggerEvent = export type Logger = { logEvent: (filename: string | null, event: LoggerEvent) => void; + debugLogIRs?: (value: CompilerPipelineValue) => void; }; export const defaultOptions: PluginOptions = { 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 90921454c8..fd361e68fa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -111,7 +111,7 @@ export type CompilerPipelineValue = | {kind: 'reactive'; name: string; value: ReactiveFunction} | {kind: 'debug'; name: string; value: string}; -export function* run( +function run( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, @@ -121,7 +121,7 @@ export function* run( logger: Logger | null, filename: string | null, code: string | null, -): Generator { +): CodegenFunction { const contextIdentifiers = findContextIdentifiers(func); const env = new Environment( func.scope, @@ -133,12 +133,17 @@ export function* run( code, useMemoCacheIdentifier, ); - yield log({ + env.logger?.debugLogIRs?.({ kind: 'debug', name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - const ast = yield* runWithEnvironment(func, env); + printLog({ + kind: 'debug', + name: 'EnvironmentConfig', + value: prettyFormat(env.config), + }); + const ast = runWithEnvironment(func, env); return ast; } @@ -146,17 +151,22 @@ export function* run( * Note: this is split from run() to make `config` out of scope, so that all * access to feature flags has to be through the Environment for consistency. */ -function* runWithEnvironment( +function runWithEnvironment( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, env: Environment, -): Generator { +): CodegenFunction { + const log = (value: CompilerPipelineValue) => { + printLog(value); + env.logger?.debugLogIRs?.(value); + return value; + }; const hir = lower(func, env).unwrap(); - yield log({kind: 'hir', name: 'HIR', value: hir}); + log({kind: 'hir', name: 'HIR', value: hir}); pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); validateContextVariableLValues(hir); validateUseMemo(hir); @@ -167,35 +177,35 @@ function* runWithEnvironment( !env.config.enableChangeDetectionForDebugging ) { dropManualMemoization(hir); - yield log({kind: 'hir', name: 'DropManualMemoization', value: hir}); + log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } inlineImmediatelyInvokedFunctionExpressions(hir); - yield log({ + log({ kind: 'hir', name: 'InlineImmediatelyInvokedFunctionExpressions', value: hir, }); mergeConsecutiveBlocks(hir); - yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); + log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); assertConsistentIdentifiers(hir); assertTerminalSuccessorsExist(hir); enterSSA(hir); - yield log({kind: 'hir', name: 'SSA', value: hir}); + log({kind: 'hir', name: 'SSA', value: hir}); eliminateRedundantPhi(hir); - yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); + log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); assertConsistentIdentifiers(hir); constantPropagation(hir); - yield log({kind: 'hir', name: 'ConstantPropagation', value: hir}); + log({kind: 'hir', name: 'ConstantPropagation', value: hir}); inferTypes(hir); - yield log({kind: 'hir', name: 'InferTypes', value: hir}); + log({kind: 'hir', name: 'InferTypes', value: hir}); if (env.config.validateHooksUsage) { validateHooksUsage(hir); @@ -210,27 +220,27 @@ function* runWithEnvironment( } analyseFunctions(hir); - yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); + log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); inferReferenceEffects(hir); - yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); + log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); validateLocalsNotReassignedAfterRender(hir); // Note: Has to come after infer reference effects because "dead" code may still affect inference deadCodeElimination(hir); - yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); + log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); if (env.config.enableInstructionReordering) { instructionReordering(hir); - yield log({kind: 'hir', name: 'InstructionReordering', value: hir}); + log({kind: 'hir', name: 'InstructionReordering', value: hir}); } pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); inferMutableRanges(hir); - yield log({kind: 'hir', name: 'InferMutableRanges', value: hir}); + log({kind: 'hir', name: 'InferMutableRanges', value: hir}); if (env.config.assertValidMutableRanges) { assertValidMutableRanges(hir); @@ -253,27 +263,27 @@ function* runWithEnvironment( } inferReactivePlaces(hir); - yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); + log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); rewriteInstructionKindsBasedOnReassignment(hir); - yield log({ + log({ kind: 'hir', name: 'RewriteInstructionKindsBasedOnReassignment', value: hir, }); propagatePhiTypes(hir); - yield log({ + log({ kind: 'hir', name: 'PropagatePhiTypes', value: hir, }); inferReactiveScopeVariables(hir); - yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); + log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir); - yield log({ + log({ kind: 'hir', name: 'MemoizeFbtAndMacroOperandsInSameScope', value: hir, @@ -285,39 +295,39 @@ function* runWithEnvironment( if (env.config.enableFunctionOutlining) { outlineFunctions(hir, fbtOperands); - yield log({kind: 'hir', name: 'OutlineFunctions', value: hir}); + log({kind: 'hir', name: 'OutlineFunctions', value: hir}); } alignMethodCallScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignMethodCallScopes', value: hir, }); alignObjectMethodScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignObjectMethodScopes', value: hir, }); pruneUnusedLabelsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PruneUnusedLabelsHIR', value: hir, }); alignReactiveScopesToBlockScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'AlignReactiveScopesToBlockScopesHIR', value: hir, }); mergeOverlappingReactiveScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'MergeOverlappingReactiveScopesHIR', value: hir, @@ -325,7 +335,7 @@ function* runWithEnvironment( assertValidBlockNesting(hir); buildReactiveScopeTerminalsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'BuildReactiveScopeTerminalsHIR', value: hir, @@ -334,14 +344,14 @@ function* runWithEnvironment( assertValidBlockNesting(hir); flattenReactiveLoopsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenReactiveLoopsHIR', value: hir, }); flattenScopesWithHooksOrUseHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenScopesWithHooksOrUseHIR', value: hir, @@ -349,7 +359,7 @@ function* runWithEnvironment( assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); propagateScopeDependenciesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PropagateScopeDependenciesHIR', value: hir, @@ -361,7 +371,7 @@ function* runWithEnvironment( if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); - yield log({ + log({ kind: 'hir', name: 'inlineJsxTransform', value: hir, @@ -369,7 +379,7 @@ function* runWithEnvironment( } const reactiveFunction = buildReactiveFunction(hir); - yield log({ + log({ kind: 'reactive', name: 'BuildReactiveFunction', value: reactiveFunction, @@ -378,7 +388,7 @@ function* runWithEnvironment( assertWellFormedBreakTargets(reactiveFunction); pruneUnusedLabels(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLabels', value: reactiveFunction, @@ -386,35 +396,35 @@ function* runWithEnvironment( assertScopeInstructionsWithinScopes(reactiveFunction); pruneNonEscapingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonEscapingScopes', value: reactiveFunction, }); pruneNonReactiveDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonReactiveDependencies', value: reactiveFunction, }); pruneUnusedScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedScopes', value: reactiveFunction, }); mergeReactiveScopesThatInvalidateTogether(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'MergeReactiveScopesThatInvalidateTogether', value: reactiveFunction, }); pruneAlwaysInvalidatingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneAlwaysInvalidatingScopes', value: reactiveFunction, @@ -422,7 +432,7 @@ function* runWithEnvironment( if (env.config.enableChangeDetectionForDebugging != null) { pruneInitializationDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneInitializationDependencies', value: reactiveFunction, @@ -430,49 +440,49 @@ function* runWithEnvironment( } propagateEarlyReturns(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PropagateEarlyReturns', value: reactiveFunction, }); pruneUnusedLValues(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLValues', value: reactiveFunction, }); promoteUsedTemporaries(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PromoteUsedTemporaries', value: reactiveFunction, }); extractScopeDeclarationsFromDestructuring(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'ExtractScopeDeclarationsFromDestructuring', value: reactiveFunction, }); stabilizeBlockIds(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'StabilizeBlockIds', value: reactiveFunction, }); const uniqueIdentifiers = renameVariables(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'RenameVariables', value: reactiveFunction, }); pruneHoistedContexts(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneHoistedContexts', value: reactiveFunction, @@ -493,9 +503,9 @@ function* runWithEnvironment( uniqueIdentifiers, fbtOperands, }).unwrap(); - yield log({kind: 'ast', name: 'Codegen', value: ast}); + log({kind: 'ast', name: 'Codegen', value: ast}); for (const outlined of ast.outlined) { - yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); + log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); } /** @@ -521,7 +531,7 @@ export function compileFn( filename: string | null, code: string | null, ): CodegenFunction { - let generator = run( + return run( func, config, fnType, @@ -530,15 +540,9 @@ export function compileFn( filename, code, ); - while (true) { - const next = generator.next(); - if (next.done) { - return next.value; - } - } } -export function log(value: CompilerPipelineValue): CompilerPipelineValue { +function printLog(value: CompilerPipelineValue): CompilerPipelineValue { switch (value.kind) { case 'ast': { logCodegenFunction(value.name, value.value); @@ -562,14 +566,3 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue { } return value; } - -export function* runPlayground( - func: NodePath< - t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression - >, - config: EnvironmentConfig, - fnType: ReactFunctionType, -): Generator { - const ast = yield* run(func, config, fnType, '_c', null, null, null); - return ast; -} 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 fa581d8ed8..2e69e6aa09 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -172,7 +172,7 @@ const EnvironmentConfigSchema = z.object({ * This is intended to support hot module reloading (HMR), where the same runtime component * instance will be reused across different versions of the component source. */ - enableResetCacheOnSourceFileChanges: z.boolean().default(false), + enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null), /** * Enable using information from existing useMemo/useCallback to understand when a value is done @@ -721,6 +721,13 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { const config = EnvironmentConfigSchema.safeParse(maybeConfig); if (config.success) { + /** + * Unless explicitly enabled, do not insert HMR handling code + * in test fixtures or playground to reduce visual noise. + */ + if (config.data.enableResetCacheOnSourceFileChanges == null) { + config.data.enableResetCacheOnSourceFileChanges = false; + } return config.data; } CompilerError.invariant(false, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts index fa43a8befe..b9c26b65b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts @@ -17,10 +17,6 @@ let ENABLED: boolean = false; let lastLogged: string; -export function toggleLogging(enabled: boolean): void { - ENABLED = enabled; -} - export function logDebug(step: string, value: string): void { if (ENABLED) { process.stdout.write(`${chalk.green(step)}:\n${value}\n\n`); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md new file mode 100644 index 0000000000..69c1b9bbbb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -0,0 +1,29 @@ + +## Input + +```javascript +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} + +``` + +## Code + +```javascript +// @compilationMode(all) +"use no memo"; + +function TestComponent({ x }) { + "use memo"; + return ; +} + +``` + +### 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/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js new file mode 100644 index 0000000000..9b314e1f99 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -0,0 +1,7 @@ +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 150df26e45..188c244d9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -17,8 +17,6 @@ export { compileFn as compile, compileProgram, parsePluginOptions, - run, - runPlayground, OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, findDirectiveEnablingMemoization, diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index f05757d3df..03bf0e784b 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -64,14 +64,12 @@ async function compile( const {Effect: EffectEnum, ValueKind: ValueKindEnum} = require( COMPILER_INDEX_PATH, ); - const {toggleLogging} = require(LOGGER_PATH); + // const {toggleLogging} = require(LOGGER_PATH); const {parseConfigPragmaForTests} = require(PARSE_CONFIG_PRAGMA_PATH) as { parseConfigPragmaForTests: typeof ParseConfigPragma; }; - // only try logging if we filtered out all but one fixture, - // since console log order is non-deterministic - toggleLogging(shouldLog); + // TODO configure debugIR logger const result = await transformFixtureInput( input, fixturePath, From ad7b4b04de5fb5ad8fa751fd7dcc446eb241e170 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 13 Dec 2024 19:35:46 -0500 Subject: [PATCH 120/422] [compiler][be] Playground now compiles entire program Compiler playground now runs the entire program through `babel-plugin-react-compiler` instead of a custom pipeline, which previously duplicated function inference logic from `Program.ts`. In addition, the playground output reflects the tranformed file (instead of a "virtual file" of manually concatenated functions). This helps with the following: - Reduce potential discrepencies between playground and babel plugin behavior. See attached fixture output for an example where we previously diverged. - Let playground users see compiler-inserted imports (e.g. `_c` or `useFire`) This also helps us repurpose playground into a more generic tool for compiler-users instead of just compiler engineers. - imports and other functions are preserved. This is important as we now differentiate between imports and globals in many cases, - playground now shows other program-changing behavior like position of outlined functions and hoisted declarations - emitted compiled functions do not need synthetic names --- .../page.spec.ts/01-user-output.txt | 3 +- .../page.spec.ts/02-default-output.txt | 3 +- .../module-scope-use-memo-output.txt | 4 +- .../module-scope-use-no-memo-output.txt | 3 +- ...cope-does-not-beat-module-scope-output.txt | 5 + .../page.spec.ts/use-memo-output.txt | 5 +- .../page.spec.ts/use-no-memo-output.txt | 8 +- .../playground/__tests__/e2e/page.spec.ts | 2 +- .../components/Editor/EditorImpl.tsx | 259 +++++------------- .../playground/components/Editor/Output.tsx | 56 ++-- compiler/apps/playground/package.json | 1 + compiler/apps/playground/yarn.lock | 20 ++ .../src/Babel/BabelPlugin.ts | 5 +- .../src/Entrypoint/Options.ts | 2 + .../src/Entrypoint/Pipeline.ts | 139 +++++----- .../src/HIR/Environment.ts | 9 +- .../src/Utils/logger.ts | 4 - ...ule-scope-usememo-function-scope.expect.md | 29 ++ ...emo-module-scope-usememo-function-scope.js | 7 + .../babel-plugin-react-compiler/src/index.ts | 2 - compiler/packages/snap/src/runner-worker.ts | 6 +- 21 files changed, 248 insertions(+), 324 deletions(-) create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt index ba680bbb57..1600f35107 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt @@ -1,4 +1,5 @@ -function TestComponent(t0) { +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt index 2cbd09bba6..1d59a120f9 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt @@ -1,4 +1,5 @@ -function MyApp() { +import { c as _c } from "react/compiler-runtime"; +export default function MyApp() { const $ = _c(1); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt index ba680bbb57..638a2bcd22 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt @@ -1,4 +1,6 @@ -function TestComponent(t0) { +"use memo"; +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt index 2c69ddc1d6..ebd2d2b046 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt @@ -1,3 +1,4 @@ -function TestComponent({ x }) { +"use no memo"; +export default function TestComponent({ x }) { return ; } diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt new file mode 100644 index 0000000000..325e6972e1 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt @@ -0,0 +1,5 @@ +"use no memo"; +function TestComponent({ x }) { + "use memo"; + return ; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt index 804bacab97..de6dd52680 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt @@ -1,3 +1,4 @@ +import { c as _c } from "react/compiler-runtime"; function TestComponent(t0) { "use memo"; const $ = _c(2); @@ -12,7 +13,7 @@ function TestComponent(t0) { } return t1; } -function anonymous_1(t0) { +const TestComponent2 = (t0) => { "use memo"; const $ = _c(2); const { x } = t0; @@ -25,4 +26,4 @@ function anonymous_1(t0) { t1 = $[1]; } return t1; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt index 5fb66309fc..02c1367622 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt @@ -1,8 +1,8 @@ -function anonymous_1() { +const TestComponent = function () { "use no memo"; return ; -} -function anonymous_3({ x }) { +}; +const TestComponent2 = ({ x }) => { "use no memo"; return ; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 846e6227bd..254002c5ce 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -55,7 +55,7 @@ const TestComponent2 = ({ x }) => { };`, }, { - name: 'function-scope-beats-module-scope', + name: 'todo-function-scope-does-not-beat-module-scope', input: ` 'use no memo'; function TestComponent({ x }) { diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 82a40272bd..002bea052e 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,23 +5,23 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse} from '@babel/parser'; +import {parse as babelParse, ParseResult} from '@babel/parser'; +import transformStripFlowTypes from "@babel/plugin-transform-flow-strip-types"; import * as HermesParser from 'hermes-parser'; -import traverse, {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import { +import BabelPluginReactCompiler, { CompilerError, CompilerErrorDetail, Effect, ErrorSeverity, parseConfigPragmaForTests, ValueKind, - runPlayground, type Hook, - findDirectiveDisablingMemoization, - findDirectiveEnablingMemoization, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, } from 'babel-plugin-react-compiler/src'; -import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment'; import clsx from 'clsx'; import invariant from 'invariant'; import {useSnackbar} from 'notistack'; @@ -44,27 +44,9 @@ import { } from './Output'; import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; +import { BabelFileResult, transformFromAstSync } from '@babel/core'; -type FunctionLike = - | NodePath - | NodePath - | NodePath; -enum MemoizeDirectiveState { - Enabled = 'Enabled', - Disabled = 'Disabled', - Undefined = 'Undefined', -} - -const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Undefined, -]); - -const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Disabled, -]); -function parseInput(input: string, language: 'flow' | 'typescript'): any { +function parseInput(input: string, language: 'flow' | 'typescript'): ParseResult { // Extract the first line to quickly check for custom test directives if (language === 'flow') { return HermesParser.parse(input, { @@ -77,95 +59,70 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { return babelParse(input, { plugins: ['typescript', 'jsx'], sourceType: 'module', - }); + }) as ParseResult; } } -function parseFunctions( +function invokeCompiler( source: string, language: 'flow' | 'typescript', -): Array<{ - compilationEnabled: boolean; - fn: FunctionLike; -}> { - const items: Array<{ - compilationEnabled: boolean; - fn: FunctionLike; - }> = []; + environment: EnvironmentConfig, + logIR: (pipelineValue: CompilerPipelineValue) => void, +): { ast: t.File, code: string, sourceMaps: BabelFileResult['map']} { + const opts: PluginOptions = parsePluginOptions({ + logger: { + debugLogIRs: logIR, + logEvent: () => {} + }, + environment, + compilationMode: 'all' + }); + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [ + [BabelPluginReactCompiler, opts], + ], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error("Expected successful compilation"); + } try { - const ast = parseInput(source, language); - traverse(ast, { - FunctionDeclaration(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - ArrowFunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - FunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - }); + babelParse(result.code, { + plugins: ['jsx'], + sourceType: 'module', + }) as ParseResult; } catch (e) { - console.error(e); - CompilerError.throwInvalidJS({ - reason: String(e), - description: null, - loc: null, - suggestions: null, - }); - } - - return items; -} - -function shouldCompile(fn: FunctionLike): boolean { - const {body} = fn.node; - if (t.isBlockStatement(body)) { - const selfCheck = checkExplicitMemoizeDirectives(body.directives); - if (selfCheck === MemoizeDirectiveState.Enabled) return true; - if (selfCheck === MemoizeDirectiveState.Disabled) return false; - - const parentWithDirective = fn.findParent(parentPath => { - if (parentPath.isBlockStatement() || parentPath.isProgram()) { - const directiveCheck = checkExplicitMemoizeDirectives( - parentPath.node.directives, - ); - return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck); + console.warn("Retrying after parse error (likely due to flow annotations): ", e) + result = transformFromAstSync(ast, source, + { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [transformStripFlowTypes], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, } - return false; - }); - - if (!parentWithDirective) return true; - const parentDirectiveCheck = checkExplicitMemoizeDirectives( - (parentWithDirective.node as t.Program | t.BlockStatement).directives, ); - return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); } - return false; -} - -function checkExplicitMemoizeDirectives( - directives: Array, -): MemoizeDirectiveState { - if (findDirectiveEnablingMemoization(directives).length) { - return MemoizeDirectiveState.Enabled; + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error("Expected successful compilation"); } - if (findDirectiveDisablingMemoization(directives).length) { - return MemoizeDirectiveState.Disabled; - } - return MemoizeDirectiveState.Undefined; + return { + ast: result.ast, + code: result.code, + sourceMaps: result.map + }; } const COMMON_HOOKS: Array<[string, Hook]> = [ @@ -216,37 +173,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ ], ]; -function isHookName(s: string): boolean { - return /^use[A-Z0-9]/.test(s); -} - -function getReactFunctionType(id: t.Identifier | null): ReactFunctionType { - if (id != null) { - if (isHookName(id.name)) { - return 'Hook'; - } - - const isPascalCaseNameSpace = /^[A-Z].*/; - if (isPascalCaseNameSpace.test(id.name)) { - return 'Component'; - } - } - return 'Other'; -} - -function getFunctionIdentifier( - fn: - | NodePath - | NodePath - | NodePath, -): t.Identifier | null { - if (fn.isArrowFunctionExpression()) { - return null; - } - const id = fn.get('id'); - return Array.isArray(id) === false && id.isIdentifier() ? id.node : null; -} - function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map>(); const error = new CompilerError(); @@ -272,63 +198,21 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { return t.identifier(`anonymous_${count++}`); } }; + let transformOutput; try { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); const config = parseConfigPragmaForTests(pragma); - const parsedFunctions = parseFunctions(source, language); - for (const func of parsedFunctions) { - const id = withIdentifier(getFunctionIdentifier(func.fn)); - const fnName = id.name; - if (!func.compilationEnabled) { - upsert({ - kind: 'ast', - fnName, - name: 'CodeGen', - value: { - type: 'FunctionDeclaration', - id: - func.fn.isArrowFunctionExpression() || - func.fn.isFunctionExpression() - ? withIdentifier(null) - : func.fn.node.id, - async: func.fn.node.async, - generator: !!func.fn.node.generator, - body: func.fn.node.body as t.BlockStatement, - params: func.fn.node.params, - }, - }); - continue; - } - for (const result of runPlayground( - func.fn, - { - ...config, - customHooks: new Map([...COMMON_HOOKS]), - }, - getReactFunctionType(id), - )) { + + transformOutput = invokeCompiler(source, language, {...config, customHooks: new Map([...COMMON_HOOKS])}, (result) => { switch (result.kind) { case 'ast': { - upsert({ - kind: 'ast', - fnName, - name: result.name, - value: { - type: 'FunctionDeclaration', - id: withIdentifier(result.value.id), - async: result.value.async, - generator: result.value.generator, - body: result.value.body, - params: result.value.params, - }, - }); break; } case 'hir': { upsert({ kind: 'hir', - fnName, + fnName: result.value.id, name: result.name, value: printFunctionWithOutlined(result.value), }); @@ -337,7 +221,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'reactive': { upsert({ kind: 'reactive', - fnName, + fnName: result.value.id, name: result.name, value: printReactiveFunctionWithOutlined(result.value), }); @@ -346,7 +230,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'debug': { upsert({ kind: 'debug', - fnName, + fnName: null, name: result.name, value: result.value, }); @@ -357,8 +241,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { throw new Error(`Unhandled result ${result}`); } } - } - } + }); } catch (err) { /** * error might be an invariant violation or other runtime error @@ -385,7 +268,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { if (error.hasErrors()) { return [{kind: 'err', results, error: error}, language]; } - return [{kind: 'ok', results}, language]; + return [{kind: 'ok', results, transformOutput}, language]; } export default function Editor(): JSX.Element { @@ -405,7 +288,7 @@ export default function Editor(): JSX.Element { } catch (e) { invariant(e instanceof Error, 'Only Error may be caught.'); enqueueSnackbar(e.message, { - variant: 'message', + variant: 'warning', ...createMessage( 'Bad URL - fell back to the default Playground.', MessageLevel.Info, diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 97a63400d2..7dab9587ad 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import generate from '@babel/generator'; import * as t from '@babel/types'; import { CodeIcon, @@ -21,17 +20,12 @@ import {memo, ReactNode, useEffect, useState} from 'react'; import {type Store} from '../../lib/stores'; import TabbedWindow from '../TabbedWindow'; import {monacoOptions} from './monacoOptions'; +import { BabelFileResult } from '@babel/core'; const MemoizedOutput = memo(Output); export default MemoizedOutput; export type PrintedCompilerPipelineValue = - | { - kind: 'ast'; - name: string; - fnName: string | null; - value: t.FunctionDeclaration; - } | { kind: 'hir'; name: string; @@ -42,7 +36,11 @@ export type PrintedCompilerPipelineValue = | {kind: 'debug'; name: string; fnName: string | null; value: string}; export type CompilerOutput = - | {kind: 'ok'; results: Map>} + | {kind: 'ok'; transformOutput: { + ast: t.File + code: string + sourceMaps: BabelFileResult['map'] + }, results: Map>} | { kind: 'err'; results: Map>; @@ -61,7 +59,6 @@ async function tabify( const tabs = new Map(); const reorderedTabs = new Map(); const concattedResults = new Map(); - let topLevelFnDecls: Array = []; // Concat all top level function declaration results into a single tab for each pass for (const [passName, results] of compilerOutput.results) { for (const result of results) { @@ -87,9 +84,6 @@ async function tabify( } break; } - case 'ast': - topLevelFnDecls.push(result.value); - break; case 'debug': { concattedResults.set(passName, result.value); break; @@ -114,13 +108,16 @@ async function tabify( lastPassOutput = text; } // Ensure that JS and the JS source map come first - if (topLevelFnDecls.length > 0) { - /** - * Make a synthetic Program so we can have a single AST with all the top level - * FunctionDeclarations - */ - const ast = t.program(topLevelFnDecls); - const {code, sourceMapUrl} = await codegen(ast, source); + if (compilerOutput.kind === 'ok') { + const sourceMapUrl = getSourceMapUrl( + compilerOutput.transformOutput.code, + JSON.stringify(compilerOutput.transformOutput.sourceMaps), + ); + const code = await prettier.format(compilerOutput.transformOutput.code, { + semi: true, + parser: 'babel', + plugins: [parserBabel, prettierPluginEstree], + }); reorderedTabs.set( 'JS', { - const generated = generate( - ast, - {sourceMaps: true, sourceFileName: 'input.js'}, - source, - ); - const sourceMapUrl = getSourceMapUrl( - generated.code, - JSON.stringify(generated.map), - ); - const codegenOutput = await prettier.format(generated.code, { - semi: true, - parser: 'babel', - plugins: [parserBabel, prettierPluginEstree], - }); - return {code: codegenOutput, sourceMapUrl}; -} - function utf16ToUTF8(s: string): string { return unescape(encodeURIComponent(s)); } diff --git a/compiler/apps/playground/package.json b/compiler/apps/playground/package.json index c3dd825f1a..e249a3f30e 100644 --- a/compiler/apps/playground/package.json +++ b/compiler/apps/playground/package.json @@ -42,6 +42,7 @@ "react-dom": "19.0.0-rc-77b637d6-20241016" }, "devDependencies": { + "@babel/plugin-transform-flow-strip-types": "^7.25.9", "@types/node": "18.11.9", "@types/react": "npm:types-react@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", diff --git a/compiler/apps/playground/yarn.lock b/compiler/apps/playground/yarn.lock index dc5362548a..3e12ad0a31 100644 --- a/compiler/apps/playground/yarn.lock +++ b/compiler/apps/playground/yarn.lock @@ -128,6 +128,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== +"@babel/helper-plugin-utils@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== + "@babel/helper-replace-supers@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9" @@ -193,6 +198,13 @@ dependencies: "@babel/types" "^7.25.6" +"@babel/plugin-syntax-flow@^7.25.9": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz#96507595c21b45fccfc2bc758d5c45452e6164fa" + integrity sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-jsx@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" @@ -214,6 +226,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-flow-strip-types@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.9.tgz#85879b42a8f5948fd6317069978e98f23ef8aec1" + integrity sha512-/VVukELzPDdci7UUsWQaSkhgnjIWXnIyRpM02ldxaVoFK96c41So8JcKT3m0gYjyv7j5FNPGS5vfELrWalkbDA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-flow" "^7.25.9" + "@babel/plugin-transform-modules-commonjs@^7.18.9", "@babel/plugin-transform-modules-commonjs@^7.24.7": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c" diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts index 401cbd4bdf..c648c66043 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -39,7 +39,10 @@ export default function BabelPluginReactCompiler( ) { opts = injectReanimatedFlag(opts); } - if (isDev) { + if ( + opts.environment.enableResetCacheOnSourceFileChanges !== false && + isDev + ) { opts = { ...opts, environment: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 72ed9e7c86..fb951d25c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -15,6 +15,7 @@ import { } from '../HIR/Environment'; import {hasOwnProperty} from '../Utils/utils'; import {fromZodError} from 'zod-validation-error'; +import {CompilerPipelineValue} from './Pipeline'; const PanicThresholdOptionsSchema = z.enum([ /* @@ -209,6 +210,7 @@ export type LoggerEvent = export type Logger = { logEvent: (filename: string | null, event: LoggerEvent) => void; + debugLogIRs?: (value: CompilerPipelineValue) => void; }; export const defaultOptions: PluginOptions = { 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 90921454c8..fd361e68fa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -111,7 +111,7 @@ export type CompilerPipelineValue = | {kind: 'reactive'; name: string; value: ReactiveFunction} | {kind: 'debug'; name: string; value: string}; -export function* run( +function run( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, @@ -121,7 +121,7 @@ export function* run( logger: Logger | null, filename: string | null, code: string | null, -): Generator { +): CodegenFunction { const contextIdentifiers = findContextIdentifiers(func); const env = new Environment( func.scope, @@ -133,12 +133,17 @@ export function* run( code, useMemoCacheIdentifier, ); - yield log({ + env.logger?.debugLogIRs?.({ kind: 'debug', name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - const ast = yield* runWithEnvironment(func, env); + printLog({ + kind: 'debug', + name: 'EnvironmentConfig', + value: prettyFormat(env.config), + }); + const ast = runWithEnvironment(func, env); return ast; } @@ -146,17 +151,22 @@ export function* run( * Note: this is split from run() to make `config` out of scope, so that all * access to feature flags has to be through the Environment for consistency. */ -function* runWithEnvironment( +function runWithEnvironment( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, env: Environment, -): Generator { +): CodegenFunction { + const log = (value: CompilerPipelineValue) => { + printLog(value); + env.logger?.debugLogIRs?.(value); + return value; + }; const hir = lower(func, env).unwrap(); - yield log({kind: 'hir', name: 'HIR', value: hir}); + log({kind: 'hir', name: 'HIR', value: hir}); pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); validateContextVariableLValues(hir); validateUseMemo(hir); @@ -167,35 +177,35 @@ function* runWithEnvironment( !env.config.enableChangeDetectionForDebugging ) { dropManualMemoization(hir); - yield log({kind: 'hir', name: 'DropManualMemoization', value: hir}); + log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } inlineImmediatelyInvokedFunctionExpressions(hir); - yield log({ + log({ kind: 'hir', name: 'InlineImmediatelyInvokedFunctionExpressions', value: hir, }); mergeConsecutiveBlocks(hir); - yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); + log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); assertConsistentIdentifiers(hir); assertTerminalSuccessorsExist(hir); enterSSA(hir); - yield log({kind: 'hir', name: 'SSA', value: hir}); + log({kind: 'hir', name: 'SSA', value: hir}); eliminateRedundantPhi(hir); - yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); + log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); assertConsistentIdentifiers(hir); constantPropagation(hir); - yield log({kind: 'hir', name: 'ConstantPropagation', value: hir}); + log({kind: 'hir', name: 'ConstantPropagation', value: hir}); inferTypes(hir); - yield log({kind: 'hir', name: 'InferTypes', value: hir}); + log({kind: 'hir', name: 'InferTypes', value: hir}); if (env.config.validateHooksUsage) { validateHooksUsage(hir); @@ -210,27 +220,27 @@ function* runWithEnvironment( } analyseFunctions(hir); - yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); + log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); inferReferenceEffects(hir); - yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); + log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); validateLocalsNotReassignedAfterRender(hir); // Note: Has to come after infer reference effects because "dead" code may still affect inference deadCodeElimination(hir); - yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); + log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); if (env.config.enableInstructionReordering) { instructionReordering(hir); - yield log({kind: 'hir', name: 'InstructionReordering', value: hir}); + log({kind: 'hir', name: 'InstructionReordering', value: hir}); } pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); inferMutableRanges(hir); - yield log({kind: 'hir', name: 'InferMutableRanges', value: hir}); + log({kind: 'hir', name: 'InferMutableRanges', value: hir}); if (env.config.assertValidMutableRanges) { assertValidMutableRanges(hir); @@ -253,27 +263,27 @@ function* runWithEnvironment( } inferReactivePlaces(hir); - yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); + log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); rewriteInstructionKindsBasedOnReassignment(hir); - yield log({ + log({ kind: 'hir', name: 'RewriteInstructionKindsBasedOnReassignment', value: hir, }); propagatePhiTypes(hir); - yield log({ + log({ kind: 'hir', name: 'PropagatePhiTypes', value: hir, }); inferReactiveScopeVariables(hir); - yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); + log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir); - yield log({ + log({ kind: 'hir', name: 'MemoizeFbtAndMacroOperandsInSameScope', value: hir, @@ -285,39 +295,39 @@ function* runWithEnvironment( if (env.config.enableFunctionOutlining) { outlineFunctions(hir, fbtOperands); - yield log({kind: 'hir', name: 'OutlineFunctions', value: hir}); + log({kind: 'hir', name: 'OutlineFunctions', value: hir}); } alignMethodCallScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignMethodCallScopes', value: hir, }); alignObjectMethodScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignObjectMethodScopes', value: hir, }); pruneUnusedLabelsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PruneUnusedLabelsHIR', value: hir, }); alignReactiveScopesToBlockScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'AlignReactiveScopesToBlockScopesHIR', value: hir, }); mergeOverlappingReactiveScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'MergeOverlappingReactiveScopesHIR', value: hir, @@ -325,7 +335,7 @@ function* runWithEnvironment( assertValidBlockNesting(hir); buildReactiveScopeTerminalsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'BuildReactiveScopeTerminalsHIR', value: hir, @@ -334,14 +344,14 @@ function* runWithEnvironment( assertValidBlockNesting(hir); flattenReactiveLoopsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenReactiveLoopsHIR', value: hir, }); flattenScopesWithHooksOrUseHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenScopesWithHooksOrUseHIR', value: hir, @@ -349,7 +359,7 @@ function* runWithEnvironment( assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); propagateScopeDependenciesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PropagateScopeDependenciesHIR', value: hir, @@ -361,7 +371,7 @@ function* runWithEnvironment( if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); - yield log({ + log({ kind: 'hir', name: 'inlineJsxTransform', value: hir, @@ -369,7 +379,7 @@ function* runWithEnvironment( } const reactiveFunction = buildReactiveFunction(hir); - yield log({ + log({ kind: 'reactive', name: 'BuildReactiveFunction', value: reactiveFunction, @@ -378,7 +388,7 @@ function* runWithEnvironment( assertWellFormedBreakTargets(reactiveFunction); pruneUnusedLabels(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLabels', value: reactiveFunction, @@ -386,35 +396,35 @@ function* runWithEnvironment( assertScopeInstructionsWithinScopes(reactiveFunction); pruneNonEscapingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonEscapingScopes', value: reactiveFunction, }); pruneNonReactiveDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonReactiveDependencies', value: reactiveFunction, }); pruneUnusedScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedScopes', value: reactiveFunction, }); mergeReactiveScopesThatInvalidateTogether(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'MergeReactiveScopesThatInvalidateTogether', value: reactiveFunction, }); pruneAlwaysInvalidatingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneAlwaysInvalidatingScopes', value: reactiveFunction, @@ -422,7 +432,7 @@ function* runWithEnvironment( if (env.config.enableChangeDetectionForDebugging != null) { pruneInitializationDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneInitializationDependencies', value: reactiveFunction, @@ -430,49 +440,49 @@ function* runWithEnvironment( } propagateEarlyReturns(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PropagateEarlyReturns', value: reactiveFunction, }); pruneUnusedLValues(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLValues', value: reactiveFunction, }); promoteUsedTemporaries(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PromoteUsedTemporaries', value: reactiveFunction, }); extractScopeDeclarationsFromDestructuring(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'ExtractScopeDeclarationsFromDestructuring', value: reactiveFunction, }); stabilizeBlockIds(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'StabilizeBlockIds', value: reactiveFunction, }); const uniqueIdentifiers = renameVariables(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'RenameVariables', value: reactiveFunction, }); pruneHoistedContexts(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneHoistedContexts', value: reactiveFunction, @@ -493,9 +503,9 @@ function* runWithEnvironment( uniqueIdentifiers, fbtOperands, }).unwrap(); - yield log({kind: 'ast', name: 'Codegen', value: ast}); + log({kind: 'ast', name: 'Codegen', value: ast}); for (const outlined of ast.outlined) { - yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); + log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); } /** @@ -521,7 +531,7 @@ export function compileFn( filename: string | null, code: string | null, ): CodegenFunction { - let generator = run( + return run( func, config, fnType, @@ -530,15 +540,9 @@ export function compileFn( filename, code, ); - while (true) { - const next = generator.next(); - if (next.done) { - return next.value; - } - } } -export function log(value: CompilerPipelineValue): CompilerPipelineValue { +function printLog(value: CompilerPipelineValue): CompilerPipelineValue { switch (value.kind) { case 'ast': { logCodegenFunction(value.name, value.value); @@ -562,14 +566,3 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue { } return value; } - -export function* runPlayground( - func: NodePath< - t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression - >, - config: EnvironmentConfig, - fnType: ReactFunctionType, -): Generator { - const ast = yield* run(func, config, fnType, '_c', null, null, null); - return ast; -} 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 fa581d8ed8..2e69e6aa09 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -172,7 +172,7 @@ const EnvironmentConfigSchema = z.object({ * This is intended to support hot module reloading (HMR), where the same runtime component * instance will be reused across different versions of the component source. */ - enableResetCacheOnSourceFileChanges: z.boolean().default(false), + enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null), /** * Enable using information from existing useMemo/useCallback to understand when a value is done @@ -721,6 +721,13 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { const config = EnvironmentConfigSchema.safeParse(maybeConfig); if (config.success) { + /** + * Unless explicitly enabled, do not insert HMR handling code + * in test fixtures or playground to reduce visual noise. + */ + if (config.data.enableResetCacheOnSourceFileChanges == null) { + config.data.enableResetCacheOnSourceFileChanges = false; + } return config.data; } CompilerError.invariant(false, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts index fa43a8befe..b9c26b65b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts @@ -17,10 +17,6 @@ let ENABLED: boolean = false; let lastLogged: string; -export function toggleLogging(enabled: boolean): void { - ENABLED = enabled; -} - export function logDebug(step: string, value: string): void { if (ENABLED) { process.stdout.write(`${chalk.green(step)}:\n${value}\n\n`); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md new file mode 100644 index 0000000000..69c1b9bbbb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -0,0 +1,29 @@ + +## Input + +```javascript +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} + +``` + +## Code + +```javascript +// @compilationMode(all) +"use no memo"; + +function TestComponent({ x }) { + "use memo"; + return ; +} + +``` + +### 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/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js new file mode 100644 index 0000000000..9b314e1f99 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -0,0 +1,7 @@ +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 150df26e45..188c244d9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -17,8 +17,6 @@ export { compileFn as compile, compileProgram, parsePluginOptions, - run, - runPlayground, OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, findDirectiveEnablingMemoization, diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index f05757d3df..03bf0e784b 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -64,14 +64,12 @@ async function compile( const {Effect: EffectEnum, ValueKind: ValueKindEnum} = require( COMPILER_INDEX_PATH, ); - const {toggleLogging} = require(LOGGER_PATH); + // const {toggleLogging} = require(LOGGER_PATH); const {parseConfigPragmaForTests} = require(PARSE_CONFIG_PRAGMA_PATH) as { parseConfigPragmaForTests: typeof ParseConfigPragma; }; - // only try logging if we filtered out all but one fixture, - // since console log order is non-deterministic - toggleLogging(shouldLog); + // TODO configure debugIR logger const result = await transformFixtureInput( input, fixturePath, From 2ea7804e5b1621a902687f5a1960ef6bf40bff37 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 13 Dec 2024 19:39:30 -0500 Subject: [PATCH 121/422] [compiler][be] Playground now compiles entire program Compiler playground now runs the entire program through `babel-plugin-react-compiler` instead of a custom pipeline which previously duplicated function inference logic from `Program.ts`. In addition, the playground output reflects the tranformed file (instead of a "virtual file" of manually concatenated functions). This helps with the following: - Reduce potential discrepencies between playground and babel plugin behavior. See attached fixture output for an example where we previously diverged. - Let playground users see compiler-inserted imports (e.g. `_c` or `useFire`) This also helps us repurpose playground into a more general tool for compiler-users instead of just for compiler engineers. - imports and other functions are preserved. We differentiate between imports and globals in many cases (e.g. `inferEffectDeps`), so it may be misleading to omit imports in printed output - playground now shows other program-changing behavior like position of outlined functions and hoisted declarations - emitted compiled functions do not need synthetic names --- .../page.spec.ts/01-user-output.txt | 3 +- .../page.spec.ts/02-default-output.txt | 3 +- .../module-scope-use-memo-output.txt | 4 +- .../module-scope-use-no-memo-output.txt | 3 +- ...cope-does-not-beat-module-scope-output.txt | 5 + .../page.spec.ts/use-memo-output.txt | 5 +- .../page.spec.ts/use-no-memo-output.txt | 8 +- .../playground/__tests__/e2e/page.spec.ts | 2 +- .../components/Editor/EditorImpl.tsx | 267 +++++------------- .../playground/components/Editor/Output.tsx | 56 ++-- compiler/apps/playground/package.json | 1 + compiler/apps/playground/yarn.lock | 20 ++ .../src/Babel/BabelPlugin.ts | 5 +- .../src/Entrypoint/Options.ts | 2 + .../src/Entrypoint/Pipeline.ts | 139 +++++---- .../src/HIR/Environment.ts | 9 +- .../src/Utils/logger.ts | 4 - ...ule-scope-usememo-function-scope.expect.md | 29 ++ ...emo-module-scope-usememo-function-scope.js | 7 + .../babel-plugin-react-compiler/src/index.ts | 2 - compiler/packages/snap/src/runner-worker.ts | 6 +- 21 files changed, 248 insertions(+), 332 deletions(-) create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt index ba680bbb57..1600f35107 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt @@ -1,4 +1,5 @@ -function TestComponent(t0) { +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt index 2cbd09bba6..1d59a120f9 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt @@ -1,4 +1,5 @@ -function MyApp() { +import { c as _c } from "react/compiler-runtime"; +export default function MyApp() { const $ = _c(1); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt index ba680bbb57..638a2bcd22 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt @@ -1,4 +1,6 @@ -function TestComponent(t0) { +"use memo"; +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt index 2c69ddc1d6..ebd2d2b046 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt @@ -1,3 +1,4 @@ -function TestComponent({ x }) { +"use no memo"; +export default function TestComponent({ x }) { return ; } diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt new file mode 100644 index 0000000000..325e6972e1 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt @@ -0,0 +1,5 @@ +"use no memo"; +function TestComponent({ x }) { + "use memo"; + return ; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt index 804bacab97..de6dd52680 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt @@ -1,3 +1,4 @@ +import { c as _c } from "react/compiler-runtime"; function TestComponent(t0) { "use memo"; const $ = _c(2); @@ -12,7 +13,7 @@ function TestComponent(t0) { } return t1; } -function anonymous_1(t0) { +const TestComponent2 = (t0) => { "use memo"; const $ = _c(2); const { x } = t0; @@ -25,4 +26,4 @@ function anonymous_1(t0) { t1 = $[1]; } return t1; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt index 5fb66309fc..02c1367622 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt @@ -1,8 +1,8 @@ -function anonymous_1() { +const TestComponent = function () { "use no memo"; return ; -} -function anonymous_3({ x }) { +}; +const TestComponent2 = ({ x }) => { "use no memo"; return ; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 846e6227bd..254002c5ce 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -55,7 +55,7 @@ const TestComponent2 = ({ x }) => { };`, }, { - name: 'function-scope-beats-module-scope', + name: 'todo-function-scope-does-not-beat-module-scope', input: ` 'use no memo'; function TestComponent({ x }) { diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 82a40272bd..2d18637c8d 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,23 +5,23 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse} from '@babel/parser'; +import {parse as babelParse, ParseResult} from '@babel/parser'; +import transformStripFlowTypes from "@babel/plugin-transform-flow-strip-types"; import * as HermesParser from 'hermes-parser'; -import traverse, {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import { +import BabelPluginReactCompiler, { CompilerError, CompilerErrorDetail, Effect, ErrorSeverity, parseConfigPragmaForTests, ValueKind, - runPlayground, type Hook, - findDirectiveDisablingMemoization, - findDirectiveEnablingMemoization, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, } from 'babel-plugin-react-compiler/src'; -import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment'; import clsx from 'clsx'; import invariant from 'invariant'; import {useSnackbar} from 'notistack'; @@ -44,27 +44,9 @@ import { } from './Output'; import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; +import { BabelFileResult, transformFromAstSync } from '@babel/core'; -type FunctionLike = - | NodePath - | NodePath - | NodePath; -enum MemoizeDirectiveState { - Enabled = 'Enabled', - Disabled = 'Disabled', - Undefined = 'Undefined', -} - -const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Undefined, -]); - -const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Disabled, -]); -function parseInput(input: string, language: 'flow' | 'typescript'): any { +function parseInput(input: string, language: 'flow' | 'typescript'): ParseResult { // Extract the first line to quickly check for custom test directives if (language === 'flow') { return HermesParser.parse(input, { @@ -77,95 +59,70 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { return babelParse(input, { plugins: ['typescript', 'jsx'], sourceType: 'module', - }); + }) as ParseResult; } } -function parseFunctions( +function invokeCompiler( source: string, language: 'flow' | 'typescript', -): Array<{ - compilationEnabled: boolean; - fn: FunctionLike; -}> { - const items: Array<{ - compilationEnabled: boolean; - fn: FunctionLike; - }> = []; + environment: EnvironmentConfig, + logIR: (pipelineValue: CompilerPipelineValue) => void, +): { ast: t.File, code: string, sourceMaps: BabelFileResult['map']} { + const opts: PluginOptions = parsePluginOptions({ + logger: { + debugLogIRs: logIR, + logEvent: () => {} + }, + environment, + compilationMode: 'all' + }); + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [ + [BabelPluginReactCompiler, opts], + ], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error("Expected successful compilation"); + } try { - const ast = parseInput(source, language); - traverse(ast, { - FunctionDeclaration(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - ArrowFunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - FunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - }); + babelParse(result.code, { + plugins: ['jsx'], + sourceType: 'module', + }) as ParseResult; } catch (e) { - console.error(e); - CompilerError.throwInvalidJS({ - reason: String(e), - description: null, - loc: null, - suggestions: null, - }); - } - - return items; -} - -function shouldCompile(fn: FunctionLike): boolean { - const {body} = fn.node; - if (t.isBlockStatement(body)) { - const selfCheck = checkExplicitMemoizeDirectives(body.directives); - if (selfCheck === MemoizeDirectiveState.Enabled) return true; - if (selfCheck === MemoizeDirectiveState.Disabled) return false; - - const parentWithDirective = fn.findParent(parentPath => { - if (parentPath.isBlockStatement() || parentPath.isProgram()) { - const directiveCheck = checkExplicitMemoizeDirectives( - parentPath.node.directives, - ); - return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck); + console.warn("Retrying after parse error (likely due to flow annotations): ", e) + result = transformFromAstSync(ast, source, + { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [transformStripFlowTypes], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, } - return false; - }); - - if (!parentWithDirective) return true; - const parentDirectiveCheck = checkExplicitMemoizeDirectives( - (parentWithDirective.node as t.Program | t.BlockStatement).directives, ); - return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); } - return false; -} - -function checkExplicitMemoizeDirectives( - directives: Array, -): MemoizeDirectiveState { - if (findDirectiveEnablingMemoization(directives).length) { - return MemoizeDirectiveState.Enabled; + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error("Expected successful compilation"); } - if (findDirectiveDisablingMemoization(directives).length) { - return MemoizeDirectiveState.Disabled; - } - return MemoizeDirectiveState.Undefined; + return { + ast: result.ast, + code: result.code, + sourceMaps: result.map + }; } const COMMON_HOOKS: Array<[string, Hook]> = [ @@ -216,37 +173,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ ], ]; -function isHookName(s: string): boolean { - return /^use[A-Z0-9]/.test(s); -} - -function getReactFunctionType(id: t.Identifier | null): ReactFunctionType { - if (id != null) { - if (isHookName(id.name)) { - return 'Hook'; - } - - const isPascalCaseNameSpace = /^[A-Z].*/; - if (isPascalCaseNameSpace.test(id.name)) { - return 'Component'; - } - } - return 'Other'; -} - -function getFunctionIdentifier( - fn: - | NodePath - | NodePath - | NodePath, -): t.Identifier | null { - if (fn.isArrowFunctionExpression()) { - return null; - } - const id = fn.get('id'); - return Array.isArray(id) === false && id.isIdentifier() ? id.node : null; -} - function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map>(); const error = new CompilerError(); @@ -264,71 +190,21 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { } else { language = 'typescript'; } - let count = 0; - const withIdentifier = (id: t.Identifier | null): t.Identifier => { - if (id != null && id.name != null) { - return id; - } else { - return t.identifier(`anonymous_${count++}`); - } - }; + let transformOutput; try { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); const config = parseConfigPragmaForTests(pragma); - const parsedFunctions = parseFunctions(source, language); - for (const func of parsedFunctions) { - const id = withIdentifier(getFunctionIdentifier(func.fn)); - const fnName = id.name; - if (!func.compilationEnabled) { - upsert({ - kind: 'ast', - fnName, - name: 'CodeGen', - value: { - type: 'FunctionDeclaration', - id: - func.fn.isArrowFunctionExpression() || - func.fn.isFunctionExpression() - ? withIdentifier(null) - : func.fn.node.id, - async: func.fn.node.async, - generator: !!func.fn.node.generator, - body: func.fn.node.body as t.BlockStatement, - params: func.fn.node.params, - }, - }); - continue; - } - for (const result of runPlayground( - func.fn, - { - ...config, - customHooks: new Map([...COMMON_HOOKS]), - }, - getReactFunctionType(id), - )) { + + transformOutput = invokeCompiler(source, language, {...config, customHooks: new Map([...COMMON_HOOKS])}, (result) => { switch (result.kind) { case 'ast': { - upsert({ - kind: 'ast', - fnName, - name: result.name, - value: { - type: 'FunctionDeclaration', - id: withIdentifier(result.value.id), - async: result.value.async, - generator: result.value.generator, - body: result.value.body, - params: result.value.params, - }, - }); break; } case 'hir': { upsert({ kind: 'hir', - fnName, + fnName: result.value.id, name: result.name, value: printFunctionWithOutlined(result.value), }); @@ -337,7 +213,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'reactive': { upsert({ kind: 'reactive', - fnName, + fnName: result.value.id, name: result.name, value: printReactiveFunctionWithOutlined(result.value), }); @@ -346,7 +222,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'debug': { upsert({ kind: 'debug', - fnName, + fnName: null, name: result.name, value: result.value, }); @@ -357,8 +233,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { throw new Error(`Unhandled result ${result}`); } } - } - } + }); } catch (err) { /** * error might be an invariant violation or other runtime error @@ -385,7 +260,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { if (error.hasErrors()) { return [{kind: 'err', results, error: error}, language]; } - return [{kind: 'ok', results}, language]; + return [{kind: 'ok', results, transformOutput}, language]; } export default function Editor(): JSX.Element { @@ -405,7 +280,7 @@ export default function Editor(): JSX.Element { } catch (e) { invariant(e instanceof Error, 'Only Error may be caught.'); enqueueSnackbar(e.message, { - variant: 'message', + variant: 'warning', ...createMessage( 'Bad URL - fell back to the default Playground.', MessageLevel.Info, diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 97a63400d2..7dab9587ad 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import generate from '@babel/generator'; import * as t from '@babel/types'; import { CodeIcon, @@ -21,17 +20,12 @@ import {memo, ReactNode, useEffect, useState} from 'react'; import {type Store} from '../../lib/stores'; import TabbedWindow from '../TabbedWindow'; import {monacoOptions} from './monacoOptions'; +import { BabelFileResult } from '@babel/core'; const MemoizedOutput = memo(Output); export default MemoizedOutput; export type PrintedCompilerPipelineValue = - | { - kind: 'ast'; - name: string; - fnName: string | null; - value: t.FunctionDeclaration; - } | { kind: 'hir'; name: string; @@ -42,7 +36,11 @@ export type PrintedCompilerPipelineValue = | {kind: 'debug'; name: string; fnName: string | null; value: string}; export type CompilerOutput = - | {kind: 'ok'; results: Map>} + | {kind: 'ok'; transformOutput: { + ast: t.File + code: string + sourceMaps: BabelFileResult['map'] + }, results: Map>} | { kind: 'err'; results: Map>; @@ -61,7 +59,6 @@ async function tabify( const tabs = new Map(); const reorderedTabs = new Map(); const concattedResults = new Map(); - let topLevelFnDecls: Array = []; // Concat all top level function declaration results into a single tab for each pass for (const [passName, results] of compilerOutput.results) { for (const result of results) { @@ -87,9 +84,6 @@ async function tabify( } break; } - case 'ast': - topLevelFnDecls.push(result.value); - break; case 'debug': { concattedResults.set(passName, result.value); break; @@ -114,13 +108,16 @@ async function tabify( lastPassOutput = text; } // Ensure that JS and the JS source map come first - if (topLevelFnDecls.length > 0) { - /** - * Make a synthetic Program so we can have a single AST with all the top level - * FunctionDeclarations - */ - const ast = t.program(topLevelFnDecls); - const {code, sourceMapUrl} = await codegen(ast, source); + if (compilerOutput.kind === 'ok') { + const sourceMapUrl = getSourceMapUrl( + compilerOutput.transformOutput.code, + JSON.stringify(compilerOutput.transformOutput.sourceMaps), + ); + const code = await prettier.format(compilerOutput.transformOutput.code, { + semi: true, + parser: 'babel', + plugins: [parserBabel, prettierPluginEstree], + }); reorderedTabs.set( 'JS', { - const generated = generate( - ast, - {sourceMaps: true, sourceFileName: 'input.js'}, - source, - ); - const sourceMapUrl = getSourceMapUrl( - generated.code, - JSON.stringify(generated.map), - ); - const codegenOutput = await prettier.format(generated.code, { - semi: true, - parser: 'babel', - plugins: [parserBabel, prettierPluginEstree], - }); - return {code: codegenOutput, sourceMapUrl}; -} - function utf16ToUTF8(s: string): string { return unescape(encodeURIComponent(s)); } diff --git a/compiler/apps/playground/package.json b/compiler/apps/playground/package.json index c3dd825f1a..e249a3f30e 100644 --- a/compiler/apps/playground/package.json +++ b/compiler/apps/playground/package.json @@ -42,6 +42,7 @@ "react-dom": "19.0.0-rc-77b637d6-20241016" }, "devDependencies": { + "@babel/plugin-transform-flow-strip-types": "^7.25.9", "@types/node": "18.11.9", "@types/react": "npm:types-react@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", diff --git a/compiler/apps/playground/yarn.lock b/compiler/apps/playground/yarn.lock index dc5362548a..3e12ad0a31 100644 --- a/compiler/apps/playground/yarn.lock +++ b/compiler/apps/playground/yarn.lock @@ -128,6 +128,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== +"@babel/helper-plugin-utils@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== + "@babel/helper-replace-supers@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9" @@ -193,6 +198,13 @@ dependencies: "@babel/types" "^7.25.6" +"@babel/plugin-syntax-flow@^7.25.9": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz#96507595c21b45fccfc2bc758d5c45452e6164fa" + integrity sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-jsx@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" @@ -214,6 +226,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-flow-strip-types@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.9.tgz#85879b42a8f5948fd6317069978e98f23ef8aec1" + integrity sha512-/VVukELzPDdci7UUsWQaSkhgnjIWXnIyRpM02ldxaVoFK96c41So8JcKT3m0gYjyv7j5FNPGS5vfELrWalkbDA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-flow" "^7.25.9" + "@babel/plugin-transform-modules-commonjs@^7.18.9", "@babel/plugin-transform-modules-commonjs@^7.24.7": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c" diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts index 401cbd4bdf..c648c66043 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -39,7 +39,10 @@ export default function BabelPluginReactCompiler( ) { opts = injectReanimatedFlag(opts); } - if (isDev) { + if ( + opts.environment.enableResetCacheOnSourceFileChanges !== false && + isDev + ) { opts = { ...opts, environment: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 72ed9e7c86..fb951d25c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -15,6 +15,7 @@ import { } from '../HIR/Environment'; import {hasOwnProperty} from '../Utils/utils'; import {fromZodError} from 'zod-validation-error'; +import {CompilerPipelineValue} from './Pipeline'; const PanicThresholdOptionsSchema = z.enum([ /* @@ -209,6 +210,7 @@ export type LoggerEvent = export type Logger = { logEvent: (filename: string | null, event: LoggerEvent) => void; + debugLogIRs?: (value: CompilerPipelineValue) => void; }; export const defaultOptions: PluginOptions = { 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 90921454c8..e8882285bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -111,7 +111,7 @@ export type CompilerPipelineValue = | {kind: 'reactive'; name: string; value: ReactiveFunction} | {kind: 'debug'; name: string; value: string}; -export function* run( +function run( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, @@ -121,7 +121,7 @@ export function* run( logger: Logger | null, filename: string | null, code: string | null, -): Generator { +): CodegenFunction { const contextIdentifiers = findContextIdentifiers(func); const env = new Environment( func.scope, @@ -133,12 +133,17 @@ export function* run( code, useMemoCacheIdentifier, ); - yield log({ + env.logger?.debugLogIRs?.({ kind: 'debug', name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - const ast = yield* runWithEnvironment(func, env); + printLog({ + kind: 'debug', + name: 'EnvironmentConfig', + value: prettyFormat(env.config), + }); + const ast = runWithEnvironment(func, env); return ast; } @@ -146,17 +151,22 @@ export function* run( * Note: this is split from run() to make `config` out of scope, so that all * access to feature flags has to be through the Environment for consistency. */ -function* runWithEnvironment( +function runWithEnvironment( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, env: Environment, -): Generator { +): CodegenFunction { + const log = (value: CompilerPipelineValue): CompilerPipelineValue => { + printLog(value); + env.logger?.debugLogIRs?.(value); + return value; + }; const hir = lower(func, env).unwrap(); - yield log({kind: 'hir', name: 'HIR', value: hir}); + log({kind: 'hir', name: 'HIR', value: hir}); pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); validateContextVariableLValues(hir); validateUseMemo(hir); @@ -167,35 +177,35 @@ function* runWithEnvironment( !env.config.enableChangeDetectionForDebugging ) { dropManualMemoization(hir); - yield log({kind: 'hir', name: 'DropManualMemoization', value: hir}); + log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } inlineImmediatelyInvokedFunctionExpressions(hir); - yield log({ + log({ kind: 'hir', name: 'InlineImmediatelyInvokedFunctionExpressions', value: hir, }); mergeConsecutiveBlocks(hir); - yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); + log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); assertConsistentIdentifiers(hir); assertTerminalSuccessorsExist(hir); enterSSA(hir); - yield log({kind: 'hir', name: 'SSA', value: hir}); + log({kind: 'hir', name: 'SSA', value: hir}); eliminateRedundantPhi(hir); - yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); + log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); assertConsistentIdentifiers(hir); constantPropagation(hir); - yield log({kind: 'hir', name: 'ConstantPropagation', value: hir}); + log({kind: 'hir', name: 'ConstantPropagation', value: hir}); inferTypes(hir); - yield log({kind: 'hir', name: 'InferTypes', value: hir}); + log({kind: 'hir', name: 'InferTypes', value: hir}); if (env.config.validateHooksUsage) { validateHooksUsage(hir); @@ -210,27 +220,27 @@ function* runWithEnvironment( } analyseFunctions(hir); - yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); + log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); inferReferenceEffects(hir); - yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); + log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); validateLocalsNotReassignedAfterRender(hir); // Note: Has to come after infer reference effects because "dead" code may still affect inference deadCodeElimination(hir); - yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); + log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); if (env.config.enableInstructionReordering) { instructionReordering(hir); - yield log({kind: 'hir', name: 'InstructionReordering', value: hir}); + log({kind: 'hir', name: 'InstructionReordering', value: hir}); } pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); inferMutableRanges(hir); - yield log({kind: 'hir', name: 'InferMutableRanges', value: hir}); + log({kind: 'hir', name: 'InferMutableRanges', value: hir}); if (env.config.assertValidMutableRanges) { assertValidMutableRanges(hir); @@ -253,27 +263,27 @@ function* runWithEnvironment( } inferReactivePlaces(hir); - yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); + log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); rewriteInstructionKindsBasedOnReassignment(hir); - yield log({ + log({ kind: 'hir', name: 'RewriteInstructionKindsBasedOnReassignment', value: hir, }); propagatePhiTypes(hir); - yield log({ + log({ kind: 'hir', name: 'PropagatePhiTypes', value: hir, }); inferReactiveScopeVariables(hir); - yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); + log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir); - yield log({ + log({ kind: 'hir', name: 'MemoizeFbtAndMacroOperandsInSameScope', value: hir, @@ -285,39 +295,39 @@ function* runWithEnvironment( if (env.config.enableFunctionOutlining) { outlineFunctions(hir, fbtOperands); - yield log({kind: 'hir', name: 'OutlineFunctions', value: hir}); + log({kind: 'hir', name: 'OutlineFunctions', value: hir}); } alignMethodCallScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignMethodCallScopes', value: hir, }); alignObjectMethodScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignObjectMethodScopes', value: hir, }); pruneUnusedLabelsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PruneUnusedLabelsHIR', value: hir, }); alignReactiveScopesToBlockScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'AlignReactiveScopesToBlockScopesHIR', value: hir, }); mergeOverlappingReactiveScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'MergeOverlappingReactiveScopesHIR', value: hir, @@ -325,7 +335,7 @@ function* runWithEnvironment( assertValidBlockNesting(hir); buildReactiveScopeTerminalsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'BuildReactiveScopeTerminalsHIR', value: hir, @@ -334,14 +344,14 @@ function* runWithEnvironment( assertValidBlockNesting(hir); flattenReactiveLoopsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenReactiveLoopsHIR', value: hir, }); flattenScopesWithHooksOrUseHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenScopesWithHooksOrUseHIR', value: hir, @@ -349,7 +359,7 @@ function* runWithEnvironment( assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); propagateScopeDependenciesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PropagateScopeDependenciesHIR', value: hir, @@ -361,7 +371,7 @@ function* runWithEnvironment( if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); - yield log({ + log({ kind: 'hir', name: 'inlineJsxTransform', value: hir, @@ -369,7 +379,7 @@ function* runWithEnvironment( } const reactiveFunction = buildReactiveFunction(hir); - yield log({ + log({ kind: 'reactive', name: 'BuildReactiveFunction', value: reactiveFunction, @@ -378,7 +388,7 @@ function* runWithEnvironment( assertWellFormedBreakTargets(reactiveFunction); pruneUnusedLabels(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLabels', value: reactiveFunction, @@ -386,35 +396,35 @@ function* runWithEnvironment( assertScopeInstructionsWithinScopes(reactiveFunction); pruneNonEscapingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonEscapingScopes', value: reactiveFunction, }); pruneNonReactiveDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonReactiveDependencies', value: reactiveFunction, }); pruneUnusedScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedScopes', value: reactiveFunction, }); mergeReactiveScopesThatInvalidateTogether(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'MergeReactiveScopesThatInvalidateTogether', value: reactiveFunction, }); pruneAlwaysInvalidatingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneAlwaysInvalidatingScopes', value: reactiveFunction, @@ -422,7 +432,7 @@ function* runWithEnvironment( if (env.config.enableChangeDetectionForDebugging != null) { pruneInitializationDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneInitializationDependencies', value: reactiveFunction, @@ -430,49 +440,49 @@ function* runWithEnvironment( } propagateEarlyReturns(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PropagateEarlyReturns', value: reactiveFunction, }); pruneUnusedLValues(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLValues', value: reactiveFunction, }); promoteUsedTemporaries(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PromoteUsedTemporaries', value: reactiveFunction, }); extractScopeDeclarationsFromDestructuring(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'ExtractScopeDeclarationsFromDestructuring', value: reactiveFunction, }); stabilizeBlockIds(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'StabilizeBlockIds', value: reactiveFunction, }); const uniqueIdentifiers = renameVariables(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'RenameVariables', value: reactiveFunction, }); pruneHoistedContexts(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneHoistedContexts', value: reactiveFunction, @@ -493,9 +503,9 @@ function* runWithEnvironment( uniqueIdentifiers, fbtOperands, }).unwrap(); - yield log({kind: 'ast', name: 'Codegen', value: ast}); + log({kind: 'ast', name: 'Codegen', value: ast}); for (const outlined of ast.outlined) { - yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); + log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); } /** @@ -521,7 +531,7 @@ export function compileFn( filename: string | null, code: string | null, ): CodegenFunction { - let generator = run( + return run( func, config, fnType, @@ -530,15 +540,9 @@ export function compileFn( filename, code, ); - while (true) { - const next = generator.next(); - if (next.done) { - return next.value; - } - } } -export function log(value: CompilerPipelineValue): CompilerPipelineValue { +function printLog(value: CompilerPipelineValue): CompilerPipelineValue { switch (value.kind) { case 'ast': { logCodegenFunction(value.name, value.value); @@ -562,14 +566,3 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue { } return value; } - -export function* runPlayground( - func: NodePath< - t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression - >, - config: EnvironmentConfig, - fnType: ReactFunctionType, -): Generator { - const ast = yield* run(func, config, fnType, '_c', null, null, null); - return ast; -} 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 fa581d8ed8..2e69e6aa09 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -172,7 +172,7 @@ const EnvironmentConfigSchema = z.object({ * This is intended to support hot module reloading (HMR), where the same runtime component * instance will be reused across different versions of the component source. */ - enableResetCacheOnSourceFileChanges: z.boolean().default(false), + enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null), /** * Enable using information from existing useMemo/useCallback to understand when a value is done @@ -721,6 +721,13 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { const config = EnvironmentConfigSchema.safeParse(maybeConfig); if (config.success) { + /** + * Unless explicitly enabled, do not insert HMR handling code + * in test fixtures or playground to reduce visual noise. + */ + if (config.data.enableResetCacheOnSourceFileChanges == null) { + config.data.enableResetCacheOnSourceFileChanges = false; + } return config.data; } CompilerError.invariant(false, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts index fa43a8befe..b9c26b65b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts @@ -17,10 +17,6 @@ let ENABLED: boolean = false; let lastLogged: string; -export function toggleLogging(enabled: boolean): void { - ENABLED = enabled; -} - export function logDebug(step: string, value: string): void { if (ENABLED) { process.stdout.write(`${chalk.green(step)}:\n${value}\n\n`); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md new file mode 100644 index 0000000000..69c1b9bbbb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -0,0 +1,29 @@ + +## Input + +```javascript +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} + +``` + +## Code + +```javascript +// @compilationMode(all) +"use no memo"; + +function TestComponent({ x }) { + "use memo"; + return ; +} + +``` + +### 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/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js new file mode 100644 index 0000000000..9b314e1f99 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -0,0 +1,7 @@ +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 150df26e45..188c244d9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -17,8 +17,6 @@ export { compileFn as compile, compileProgram, parsePluginOptions, - run, - runPlayground, OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, findDirectiveEnablingMemoization, diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index f05757d3df..03bf0e784b 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -64,14 +64,12 @@ async function compile( const {Effect: EffectEnum, ValueKind: ValueKindEnum} = require( COMPILER_INDEX_PATH, ); - const {toggleLogging} = require(LOGGER_PATH); + // const {toggleLogging} = require(LOGGER_PATH); const {parseConfigPragmaForTests} = require(PARSE_CONFIG_PRAGMA_PATH) as { parseConfigPragmaForTests: typeof ParseConfigPragma; }; - // only try logging if we filtered out all but one fixture, - // since console log order is non-deterministic - toggleLogging(shouldLog); + // TODO configure debugIR logger const result = await transformFixtureInput( input, fixturePath, From 76d23c2391218de5d3772c07dccd12473fd6de1c Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Sat, 14 Dec 2024 15:02:02 -0500 Subject: [PATCH 122/422] [compiler][be] Playground now compiles entire program Compiler playground now runs the entire program through `babel-plugin-react-compiler` instead of a custom pipeline which previously duplicated function inference logic from `Program.ts`. In addition, the playground output reflects the tranformed file (instead of a "virtual file" of manually concatenated functions). This helps with the following: - Reduce potential discrepencies between playground and babel plugin behavior. See attached fixture output for an example where we previously diverged. - Let playground users see compiler-inserted imports (e.g. `_c` or `useFire`) This also helps us repurpose playground into a more general tool for compiler-users instead of just for compiler engineers. - imports and other functions are preserved. We differentiate between imports and globals in many cases (e.g. `inferEffectDeps`), so it may be misleading to omit imports in printed output - playground now shows other program-changing behavior like position of outlined functions and hoisted declarations - emitted compiled functions do not need synthetic names --- .../page.spec.ts/01-user-output.txt | 3 +- .../page.spec.ts/02-default-output.txt | 3 +- .../module-scope-use-memo-output.txt | 4 +- .../module-scope-use-no-memo-output.txt | 3 +- ...cope-does-not-beat-module-scope-output.txt | 5 + .../page.spec.ts/use-memo-output.txt | 5 +- .../page.spec.ts/use-no-memo-output.txt | 8 +- .../playground/__tests__/e2e/page.spec.ts | 2 +- .../components/Editor/EditorImpl.tsx | 259 ++++-------------- .../playground/components/Editor/Output.tsx | 63 ++--- .../src/Babel/BabelPlugin.ts | 5 +- .../src/Entrypoint/Options.ts | 2 + .../src/Entrypoint/Pipeline.ts | 139 +++++----- .../src/HIR/Environment.ts | 29 +- ...ule-scope-usememo-function-scope.expect.md | 29 ++ ...emo-module-scope-usememo-function-scope.js | 7 + .../babel-plugin-react-compiler/src/index.ts | 2 - 17 files changed, 233 insertions(+), 335 deletions(-) create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt index ba680bbb57..1600f35107 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt @@ -1,4 +1,5 @@ -function TestComponent(t0) { +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt index 2cbd09bba6..1d59a120f9 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt @@ -1,4 +1,5 @@ -function MyApp() { +import { c as _c } from "react/compiler-runtime"; +export default function MyApp() { const $ = _c(1); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt index ba680bbb57..638a2bcd22 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt @@ -1,4 +1,6 @@ -function TestComponent(t0) { +"use memo"; +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt index 2c69ddc1d6..ebd2d2b046 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt @@ -1,3 +1,4 @@ -function TestComponent({ x }) { +"use no memo"; +export default function TestComponent({ x }) { return ; } diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt new file mode 100644 index 0000000000..325e6972e1 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt @@ -0,0 +1,5 @@ +"use no memo"; +function TestComponent({ x }) { + "use memo"; + return ; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt index 804bacab97..de6dd52680 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt @@ -1,3 +1,4 @@ +import { c as _c } from "react/compiler-runtime"; function TestComponent(t0) { "use memo"; const $ = _c(2); @@ -12,7 +13,7 @@ function TestComponent(t0) { } return t1; } -function anonymous_1(t0) { +const TestComponent2 = (t0) => { "use memo"; const $ = _c(2); const { x } = t0; @@ -25,4 +26,4 @@ function anonymous_1(t0) { t1 = $[1]; } return t1; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt index 5fb66309fc..02c1367622 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt @@ -1,8 +1,8 @@ -function anonymous_1() { +const TestComponent = function () { "use no memo"; return ; -} -function anonymous_3({ x }) { +}; +const TestComponent2 = ({ x }) => { "use no memo"; return ; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 846e6227bd..254002c5ce 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -55,7 +55,7 @@ const TestComponent2 = ({ x }) => { };`, }, { - name: 'function-scope-beats-module-scope', + name: 'todo-function-scope-does-not-beat-module-scope', input: ` 'use no memo'; function TestComponent({ x }) { diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 82a40272bd..236d642a34 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,23 +5,22 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse} from '@babel/parser'; +import {parse as babelParse, ParseResult} from '@babel/parser'; import * as HermesParser from 'hermes-parser'; -import traverse, {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import { +import BabelPluginReactCompiler, { CompilerError, CompilerErrorDetail, Effect, ErrorSeverity, parseConfigPragmaForTests, ValueKind, - runPlayground, type Hook, - findDirectiveDisablingMemoization, - findDirectiveEnablingMemoization, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, } from 'babel-plugin-react-compiler/src'; -import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment'; import clsx from 'clsx'; import invariant from 'invariant'; import {useSnackbar} from 'notistack'; @@ -39,32 +38,18 @@ import {useStore, useStoreDispatch} from '../StoreContext'; import Input from './Input'; import { CompilerOutput, + CompilerTransformOutput, default as Output, PrintedCompilerPipelineValue, } from './Output'; import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; +import {transformFromAstSync} from '@babel/core'; -type FunctionLike = - | NodePath - | NodePath - | NodePath; -enum MemoizeDirectiveState { - Enabled = 'Enabled', - Disabled = 'Disabled', - Undefined = 'Undefined', -} - -const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Undefined, -]); - -const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Disabled, -]); -function parseInput(input: string, language: 'flow' | 'typescript'): any { +function parseInput( + input: string, + language: 'flow' | 'typescript', +): ParseResult { // Extract the first line to quickly check for custom test directives if (language === 'flow') { return HermesParser.parse(input, { @@ -77,95 +62,44 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { return babelParse(input, { plugins: ['typescript', 'jsx'], sourceType: 'module', - }); + }) as ParseResult; } } -function parseFunctions( +function invokeCompiler( source: string, language: 'flow' | 'typescript', -): Array<{ - compilationEnabled: boolean; - fn: FunctionLike; -}> { - const items: Array<{ - compilationEnabled: boolean; - fn: FunctionLike; - }> = []; - try { - const ast = parseInput(source, language); - traverse(ast, { - FunctionDeclaration(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - ArrowFunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - FunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - }); - } catch (e) { - console.error(e); - CompilerError.throwInvalidJS({ - reason: String(e), - description: null, - loc: null, - suggestions: null, - }); + environment: EnvironmentConfig, + logIR: (pipelineValue: CompilerPipelineValue) => void, +): CompilerTransformOutput { + const opts: PluginOptions = parsePluginOptions({ + logger: { + debugLogIRs: logIR, + logEvent: () => {}, + }, + environment, + compilationMode: 'all', + }); + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [[BabelPluginReactCompiler, opts]], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error('Expected successful compilation'); } - - return items; -} - -function shouldCompile(fn: FunctionLike): boolean { - const {body} = fn.node; - if (t.isBlockStatement(body)) { - const selfCheck = checkExplicitMemoizeDirectives(body.directives); - if (selfCheck === MemoizeDirectiveState.Enabled) return true; - if (selfCheck === MemoizeDirectiveState.Disabled) return false; - - const parentWithDirective = fn.findParent(parentPath => { - if (parentPath.isBlockStatement() || parentPath.isProgram()) { - const directiveCheck = checkExplicitMemoizeDirectives( - parentPath.node.directives, - ); - return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck); - } - return false; - }); - - if (!parentWithDirective) return true; - const parentDirectiveCheck = checkExplicitMemoizeDirectives( - (parentWithDirective.node as t.Program | t.BlockStatement).directives, - ); - return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); - } - return false; -} - -function checkExplicitMemoizeDirectives( - directives: Array, -): MemoizeDirectiveState { - if (findDirectiveEnablingMemoization(directives).length) { - return MemoizeDirectiveState.Enabled; - } - if (findDirectiveDisablingMemoization(directives).length) { - return MemoizeDirectiveState.Disabled; - } - return MemoizeDirectiveState.Undefined; + return { + code: result.code, + sourceMaps: result.map, + language, + }; } const COMMON_HOOKS: Array<[string, Hook]> = [ @@ -216,37 +150,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ ], ]; -function isHookName(s: string): boolean { - return /^use[A-Z0-9]/.test(s); -} - -function getReactFunctionType(id: t.Identifier | null): ReactFunctionType { - if (id != null) { - if (isHookName(id.name)) { - return 'Hook'; - } - - const isPascalCaseNameSpace = /^[A-Z].*/; - if (isPascalCaseNameSpace.test(id.name)) { - return 'Component'; - } - } - return 'Other'; -} - -function getFunctionIdentifier( - fn: - | NodePath - | NodePath - | NodePath, -): t.Identifier | null { - if (fn.isArrowFunctionExpression()) { - return null; - } - const id = fn.get('id'); - return Array.isArray(id) === false && id.isIdentifier() ? id.node : null; -} - function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map>(); const error = new CompilerError(); @@ -264,71 +167,25 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { } else { language = 'typescript'; } - let count = 0; - const withIdentifier = (id: t.Identifier | null): t.Identifier => { - if (id != null && id.name != null) { - return id; - } else { - return t.identifier(`anonymous_${count++}`); - } - }; + let transformOutput; try { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); const config = parseConfigPragmaForTests(pragma); - const parsedFunctions = parseFunctions(source, language); - for (const func of parsedFunctions) { - const id = withIdentifier(getFunctionIdentifier(func.fn)); - const fnName = id.name; - if (!func.compilationEnabled) { - upsert({ - kind: 'ast', - fnName, - name: 'CodeGen', - value: { - type: 'FunctionDeclaration', - id: - func.fn.isArrowFunctionExpression() || - func.fn.isFunctionExpression() - ? withIdentifier(null) - : func.fn.node.id, - async: func.fn.node.async, - generator: !!func.fn.node.generator, - body: func.fn.node.body as t.BlockStatement, - params: func.fn.node.params, - }, - }); - continue; - } - for (const result of runPlayground( - func.fn, - { - ...config, - customHooks: new Map([...COMMON_HOOKS]), - }, - getReactFunctionType(id), - )) { + + transformOutput = invokeCompiler( + source, + language, + {...config, customHooks: new Map([...COMMON_HOOKS])}, + result => { switch (result.kind) { case 'ast': { - upsert({ - kind: 'ast', - fnName, - name: result.name, - value: { - type: 'FunctionDeclaration', - id: withIdentifier(result.value.id), - async: result.value.async, - generator: result.value.generator, - body: result.value.body, - params: result.value.params, - }, - }); break; } case 'hir': { upsert({ kind: 'hir', - fnName, + fnName: result.value.id, name: result.name, value: printFunctionWithOutlined(result.value), }); @@ -337,7 +194,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'reactive': { upsert({ kind: 'reactive', - fnName, + fnName: result.value.id, name: result.name, value: printReactiveFunctionWithOutlined(result.value), }); @@ -346,7 +203,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'debug': { upsert({ kind: 'debug', - fnName, + fnName: null, name: result.name, value: result.value, }); @@ -357,8 +214,8 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { throw new Error(`Unhandled result ${result}`); } } - } - } + }, + ); } catch (err) { /** * error might be an invariant violation or other runtime error @@ -385,7 +242,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { if (error.hasErrors()) { return [{kind: 'err', results, error: error}, language]; } - return [{kind: 'ok', results}, language]; + return [{kind: 'ok', results, transformOutput}, language]; } export default function Editor(): JSX.Element { @@ -405,7 +262,7 @@ export default function Editor(): JSX.Element { } catch (e) { invariant(e instanceof Error, 'Only Error may be caught.'); enqueueSnackbar(e.message, { - variant: 'message', + variant: 'warning', ...createMessage( 'Bad URL - fell back to the default Playground.', MessageLevel.Info, diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 97a63400d2..d4127c63cf 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -5,8 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import generate from '@babel/generator'; -import * as t from '@babel/types'; import { CodeIcon, DocumentAddIcon, @@ -21,17 +19,12 @@ import {memo, ReactNode, useEffect, useState} from 'react'; import {type Store} from '../../lib/stores'; import TabbedWindow from '../TabbedWindow'; import {monacoOptions} from './monacoOptions'; +import {BabelFileResult} from '@babel/core'; const MemoizedOutput = memo(Output); export default MemoizedOutput; export type PrintedCompilerPipelineValue = - | { - kind: 'ast'; - name: string; - fnName: string | null; - value: t.FunctionDeclaration; - } | { kind: 'hir'; name: string; @@ -41,8 +34,17 @@ export type PrintedCompilerPipelineValue = | {kind: 'reactive'; name: string; fnName: string | null; value: string} | {kind: 'debug'; name: string; fnName: string | null; value: string}; +export type CompilerTransformOutput = { + code: string; + sourceMaps: BabelFileResult['map']; + language: 'flow' | 'typescript'; +}; export type CompilerOutput = - | {kind: 'ok'; results: Map>} + | { + kind: 'ok'; + transformOutput: CompilerTransformOutput; + results: Map>; + } | { kind: 'err'; results: Map>; @@ -61,7 +63,6 @@ async function tabify( const tabs = new Map(); const reorderedTabs = new Map(); const concattedResults = new Map(); - let topLevelFnDecls: Array = []; // Concat all top level function declaration results into a single tab for each pass for (const [passName, results] of compilerOutput.results) { for (const result of results) { @@ -87,9 +88,6 @@ async function tabify( } break; } - case 'ast': - topLevelFnDecls.push(result.value); - break; case 'debug': { concattedResults.set(passName, result.value); break; @@ -114,13 +112,17 @@ async function tabify( lastPassOutput = text; } // Ensure that JS and the JS source map come first - if (topLevelFnDecls.length > 0) { - /** - * Make a synthetic Program so we can have a single AST with all the top level - * FunctionDeclarations - */ - const ast = t.program(topLevelFnDecls); - const {code, sourceMapUrl} = await codegen(ast, source); + if (compilerOutput.kind === 'ok') { + const {transformOutput} = compilerOutput; + const sourceMapUrl = getSourceMapUrl( + transformOutput.code, + JSON.stringify(transformOutput.sourceMaps), + ); + const code = await prettier.format(transformOutput.code, { + semi: true, + parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts', + plugins: [parserBabel, prettierPluginEstree], + }); reorderedTabs.set( 'JS', { - const generated = generate( - ast, - {sourceMaps: true, sourceFileName: 'input.js'}, - source, - ); - const sourceMapUrl = getSourceMapUrl( - generated.code, - JSON.stringify(generated.map), - ); - const codegenOutput = await prettier.format(generated.code, { - semi: true, - parser: 'babel', - plugins: [parserBabel, prettierPluginEstree], - }); - return {code: codegenOutput, sourceMapUrl}; -} - function utf16ToUTF8(s: string): string { return unescape(encodeURIComponent(s)); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts index 401cbd4bdf..c648c66043 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -39,7 +39,10 @@ export default function BabelPluginReactCompiler( ) { opts = injectReanimatedFlag(opts); } - if (isDev) { + if ( + opts.environment.enableResetCacheOnSourceFileChanges !== false && + isDev + ) { opts = { ...opts, environment: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 72ed9e7c86..fb951d25c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -15,6 +15,7 @@ import { } from '../HIR/Environment'; import {hasOwnProperty} from '../Utils/utils'; import {fromZodError} from 'zod-validation-error'; +import {CompilerPipelineValue} from './Pipeline'; const PanicThresholdOptionsSchema = z.enum([ /* @@ -209,6 +210,7 @@ export type LoggerEvent = export type Logger = { logEvent: (filename: string | null, event: LoggerEvent) => void; + debugLogIRs?: (value: CompilerPipelineValue) => void; }; export const defaultOptions: PluginOptions = { 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 90921454c8..e8882285bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -111,7 +111,7 @@ export type CompilerPipelineValue = | {kind: 'reactive'; name: string; value: ReactiveFunction} | {kind: 'debug'; name: string; value: string}; -export function* run( +function run( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, @@ -121,7 +121,7 @@ export function* run( logger: Logger | null, filename: string | null, code: string | null, -): Generator { +): CodegenFunction { const contextIdentifiers = findContextIdentifiers(func); const env = new Environment( func.scope, @@ -133,12 +133,17 @@ export function* run( code, useMemoCacheIdentifier, ); - yield log({ + env.logger?.debugLogIRs?.({ kind: 'debug', name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - const ast = yield* runWithEnvironment(func, env); + printLog({ + kind: 'debug', + name: 'EnvironmentConfig', + value: prettyFormat(env.config), + }); + const ast = runWithEnvironment(func, env); return ast; } @@ -146,17 +151,22 @@ export function* run( * Note: this is split from run() to make `config` out of scope, so that all * access to feature flags has to be through the Environment for consistency. */ -function* runWithEnvironment( +function runWithEnvironment( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, env: Environment, -): Generator { +): CodegenFunction { + const log = (value: CompilerPipelineValue): CompilerPipelineValue => { + printLog(value); + env.logger?.debugLogIRs?.(value); + return value; + }; const hir = lower(func, env).unwrap(); - yield log({kind: 'hir', name: 'HIR', value: hir}); + log({kind: 'hir', name: 'HIR', value: hir}); pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); validateContextVariableLValues(hir); validateUseMemo(hir); @@ -167,35 +177,35 @@ function* runWithEnvironment( !env.config.enableChangeDetectionForDebugging ) { dropManualMemoization(hir); - yield log({kind: 'hir', name: 'DropManualMemoization', value: hir}); + log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } inlineImmediatelyInvokedFunctionExpressions(hir); - yield log({ + log({ kind: 'hir', name: 'InlineImmediatelyInvokedFunctionExpressions', value: hir, }); mergeConsecutiveBlocks(hir); - yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); + log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); assertConsistentIdentifiers(hir); assertTerminalSuccessorsExist(hir); enterSSA(hir); - yield log({kind: 'hir', name: 'SSA', value: hir}); + log({kind: 'hir', name: 'SSA', value: hir}); eliminateRedundantPhi(hir); - yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); + log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); assertConsistentIdentifiers(hir); constantPropagation(hir); - yield log({kind: 'hir', name: 'ConstantPropagation', value: hir}); + log({kind: 'hir', name: 'ConstantPropagation', value: hir}); inferTypes(hir); - yield log({kind: 'hir', name: 'InferTypes', value: hir}); + log({kind: 'hir', name: 'InferTypes', value: hir}); if (env.config.validateHooksUsage) { validateHooksUsage(hir); @@ -210,27 +220,27 @@ function* runWithEnvironment( } analyseFunctions(hir); - yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); + log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); inferReferenceEffects(hir); - yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); + log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); validateLocalsNotReassignedAfterRender(hir); // Note: Has to come after infer reference effects because "dead" code may still affect inference deadCodeElimination(hir); - yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); + log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); if (env.config.enableInstructionReordering) { instructionReordering(hir); - yield log({kind: 'hir', name: 'InstructionReordering', value: hir}); + log({kind: 'hir', name: 'InstructionReordering', value: hir}); } pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); inferMutableRanges(hir); - yield log({kind: 'hir', name: 'InferMutableRanges', value: hir}); + log({kind: 'hir', name: 'InferMutableRanges', value: hir}); if (env.config.assertValidMutableRanges) { assertValidMutableRanges(hir); @@ -253,27 +263,27 @@ function* runWithEnvironment( } inferReactivePlaces(hir); - yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); + log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); rewriteInstructionKindsBasedOnReassignment(hir); - yield log({ + log({ kind: 'hir', name: 'RewriteInstructionKindsBasedOnReassignment', value: hir, }); propagatePhiTypes(hir); - yield log({ + log({ kind: 'hir', name: 'PropagatePhiTypes', value: hir, }); inferReactiveScopeVariables(hir); - yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); + log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir); - yield log({ + log({ kind: 'hir', name: 'MemoizeFbtAndMacroOperandsInSameScope', value: hir, @@ -285,39 +295,39 @@ function* runWithEnvironment( if (env.config.enableFunctionOutlining) { outlineFunctions(hir, fbtOperands); - yield log({kind: 'hir', name: 'OutlineFunctions', value: hir}); + log({kind: 'hir', name: 'OutlineFunctions', value: hir}); } alignMethodCallScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignMethodCallScopes', value: hir, }); alignObjectMethodScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignObjectMethodScopes', value: hir, }); pruneUnusedLabelsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PruneUnusedLabelsHIR', value: hir, }); alignReactiveScopesToBlockScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'AlignReactiveScopesToBlockScopesHIR', value: hir, }); mergeOverlappingReactiveScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'MergeOverlappingReactiveScopesHIR', value: hir, @@ -325,7 +335,7 @@ function* runWithEnvironment( assertValidBlockNesting(hir); buildReactiveScopeTerminalsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'BuildReactiveScopeTerminalsHIR', value: hir, @@ -334,14 +344,14 @@ function* runWithEnvironment( assertValidBlockNesting(hir); flattenReactiveLoopsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenReactiveLoopsHIR', value: hir, }); flattenScopesWithHooksOrUseHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenScopesWithHooksOrUseHIR', value: hir, @@ -349,7 +359,7 @@ function* runWithEnvironment( assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); propagateScopeDependenciesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PropagateScopeDependenciesHIR', value: hir, @@ -361,7 +371,7 @@ function* runWithEnvironment( if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); - yield log({ + log({ kind: 'hir', name: 'inlineJsxTransform', value: hir, @@ -369,7 +379,7 @@ function* runWithEnvironment( } const reactiveFunction = buildReactiveFunction(hir); - yield log({ + log({ kind: 'reactive', name: 'BuildReactiveFunction', value: reactiveFunction, @@ -378,7 +388,7 @@ function* runWithEnvironment( assertWellFormedBreakTargets(reactiveFunction); pruneUnusedLabels(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLabels', value: reactiveFunction, @@ -386,35 +396,35 @@ function* runWithEnvironment( assertScopeInstructionsWithinScopes(reactiveFunction); pruneNonEscapingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonEscapingScopes', value: reactiveFunction, }); pruneNonReactiveDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonReactiveDependencies', value: reactiveFunction, }); pruneUnusedScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedScopes', value: reactiveFunction, }); mergeReactiveScopesThatInvalidateTogether(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'MergeReactiveScopesThatInvalidateTogether', value: reactiveFunction, }); pruneAlwaysInvalidatingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneAlwaysInvalidatingScopes', value: reactiveFunction, @@ -422,7 +432,7 @@ function* runWithEnvironment( if (env.config.enableChangeDetectionForDebugging != null) { pruneInitializationDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneInitializationDependencies', value: reactiveFunction, @@ -430,49 +440,49 @@ function* runWithEnvironment( } propagateEarlyReturns(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PropagateEarlyReturns', value: reactiveFunction, }); pruneUnusedLValues(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLValues', value: reactiveFunction, }); promoteUsedTemporaries(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PromoteUsedTemporaries', value: reactiveFunction, }); extractScopeDeclarationsFromDestructuring(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'ExtractScopeDeclarationsFromDestructuring', value: reactiveFunction, }); stabilizeBlockIds(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'StabilizeBlockIds', value: reactiveFunction, }); const uniqueIdentifiers = renameVariables(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'RenameVariables', value: reactiveFunction, }); pruneHoistedContexts(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneHoistedContexts', value: reactiveFunction, @@ -493,9 +503,9 @@ function* runWithEnvironment( uniqueIdentifiers, fbtOperands, }).unwrap(); - yield log({kind: 'ast', name: 'Codegen', value: ast}); + log({kind: 'ast', name: 'Codegen', value: ast}); for (const outlined of ast.outlined) { - yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); + log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); } /** @@ -521,7 +531,7 @@ export function compileFn( filename: string | null, code: string | null, ): CodegenFunction { - let generator = run( + return run( func, config, fnType, @@ -530,15 +540,9 @@ export function compileFn( filename, code, ); - while (true) { - const next = generator.next(); - if (next.done) { - return next.value; - } - } } -export function log(value: CompilerPipelineValue): CompilerPipelineValue { +function printLog(value: CompilerPipelineValue): CompilerPipelineValue { switch (value.kind) { case 'ast': { logCodegenFunction(value.name, value.value); @@ -562,14 +566,3 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue { } return value; } - -export function* runPlayground( - func: NodePath< - t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression - >, - config: EnvironmentConfig, - fnType: ReactFunctionType, -): Generator { - const ast = yield* run(func, config, fnType, '_c', null, null, null); - return ast; -} 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 fa581d8ed8..48589b8be9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -168,11 +168,19 @@ const EnvironmentConfigSchema = z.object({ customMacros: z.nullable(z.array(MacroSchema)).default(null), /** - * Enable a check that resets the memoization cache when the source code of the file changes. - * This is intended to support hot module reloading (HMR), where the same runtime component - * instance will be reused across different versions of the component source. + * Enable a check that resets the memoization cache when the source code of + * the file changes. This is intended to support hot module reloading (HMR), + * where the same runtime component instance will be reused across different + * versions of the component source. + * + * When set to + * - true: code for HMR support is always generated, regardless of NODE_ENV + * or `globalThis.__DEV__` + * - false: code for HMR support is not generated + * - null: (default) code for HMR support is conditionally generated dependent + * on `NODE_ENV` and `globalThis.__DEV__` at the time of compilation. */ - enableResetCacheOnSourceFileChanges: z.boolean().default(false), + enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null), /** * Enable using information from existing useMemo/useCallback to understand when a value is done @@ -708,7 +716,10 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { continue; } - if (typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean') { + if ( + key !== 'enableResetCacheOnSourceFileChanges' && + typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean' + ) { // skip parsing non-boolean properties continue; } @@ -718,9 +729,15 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { maybeConfig[key] = false; } } - const config = EnvironmentConfigSchema.safeParse(maybeConfig); if (config.success) { + /** + * Unless explicitly enabled, do not insert HMR handling code + * in test fixtures or playground to reduce visual noise. + */ + if (config.data.enableResetCacheOnSourceFileChanges == null) { + config.data.enableResetCacheOnSourceFileChanges = false; + } return config.data; } CompilerError.invariant(false, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md new file mode 100644 index 0000000000..69c1b9bbbb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -0,0 +1,29 @@ + +## Input + +```javascript +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} + +``` + +## Code + +```javascript +// @compilationMode(all) +"use no memo"; + +function TestComponent({ x }) { + "use memo"; + return ; +} + +``` + +### 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/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js new file mode 100644 index 0000000000..9b314e1f99 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -0,0 +1,7 @@ +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 150df26e45..188c244d9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -17,8 +17,6 @@ export { compileFn as compile, compileProgram, parsePluginOptions, - run, - runPlayground, OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, findDirectiveEnablingMemoization, From 46f738a2e8e0eba271fd774276040e4eb0bbd799 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 12:06:48 -0500 Subject: [PATCH 123/422] [compiler][be] Playground now compiles entire program Compiler playground now runs the entire program through `babel-plugin-react-compiler` instead of a custom pipeline which previously duplicated function inference logic from `Program.ts`. In addition, the playground output reflects the tranformed file (instead of a "virtual file" of manually concatenated functions). This helps with the following: - Reduce potential discrepencies between playground and babel plugin behavior. See attached fixture output for an example where we previously diverged. - Let playground users see compiler-inserted imports (e.g. `_c` or `useFire`) This also helps us repurpose playground into a more general tool for compiler-users instead of just for compiler engineers. - imports and other functions are preserved. We differentiate between imports and globals in many cases (e.g. `inferEffectDeps`), so it may be misleading to omit imports in printed output - playground now shows other program-changing behavior like position of outlined functions and hoisted declarations - emitted compiled functions do not need synthetic names --- .../page.spec.ts/01-user-output.txt | 3 +- .../page.spec.ts/02-default-output.txt | 3 +- .../module-scope-use-memo-output.txt | 4 +- .../module-scope-use-no-memo-output.txt | 3 +- ...cope-does-not-beat-module-scope-output.txt | 5 + .../page.spec.ts/use-memo-output.txt | 5 +- .../page.spec.ts/use-no-memo-output.txt | 8 +- .../playground/__tests__/e2e/page.spec.ts | 2 +- .../components/Editor/EditorImpl.tsx | 260 ++++-------------- .../playground/components/Editor/Output.tsx | 63 ++--- .../src/Babel/BabelPlugin.ts | 5 +- .../src/Entrypoint/Options.ts | 2 + .../src/Entrypoint/Pipeline.ts | 139 +++++----- .../src/HIR/Environment.ts | 29 +- ...ule-scope-usememo-function-scope.expect.md | 29 ++ ...emo-module-scope-usememo-function-scope.js | 7 + .../babel-plugin-react-compiler/src/index.ts | 2 - 17 files changed, 234 insertions(+), 335 deletions(-) create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt index ba680bbb57..1600f35107 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt @@ -1,4 +1,5 @@ -function TestComponent(t0) { +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt index 2cbd09bba6..1d59a120f9 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt @@ -1,4 +1,5 @@ -function MyApp() { +import { c as _c } from "react/compiler-runtime"; +export default function MyApp() { const $ = _c(1); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt index ba680bbb57..638a2bcd22 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt @@ -1,4 +1,6 @@ -function TestComponent(t0) { +"use memo"; +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt index 2c69ddc1d6..ebd2d2b046 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt @@ -1,3 +1,4 @@ -function TestComponent({ x }) { +"use no memo"; +export default function TestComponent({ x }) { return ; } diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt new file mode 100644 index 0000000000..325e6972e1 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt @@ -0,0 +1,5 @@ +"use no memo"; +function TestComponent({ x }) { + "use memo"; + return ; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt index 804bacab97..de6dd52680 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt @@ -1,3 +1,4 @@ +import { c as _c } from "react/compiler-runtime"; function TestComponent(t0) { "use memo"; const $ = _c(2); @@ -12,7 +13,7 @@ function TestComponent(t0) { } return t1; } -function anonymous_1(t0) { +const TestComponent2 = (t0) => { "use memo"; const $ = _c(2); const { x } = t0; @@ -25,4 +26,4 @@ function anonymous_1(t0) { t1 = $[1]; } return t1; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt index 5fb66309fc..02c1367622 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt @@ -1,8 +1,8 @@ -function anonymous_1() { +const TestComponent = function () { "use no memo"; return ; -} -function anonymous_3({ x }) { +}; +const TestComponent2 = ({ x }) => { "use no memo"; return ; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 846e6227bd..254002c5ce 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -55,7 +55,7 @@ const TestComponent2 = ({ x }) => { };`, }, { - name: 'function-scope-beats-module-scope', + name: 'todo-function-scope-does-not-beat-module-scope', input: ` 'use no memo'; function TestComponent({ x }) { diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 82a40272bd..785b9fd075 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,23 +5,22 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse} from '@babel/parser'; +import {parse as babelParse, ParseResult} from '@babel/parser'; import * as HermesParser from 'hermes-parser'; -import traverse, {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import { +import BabelPluginReactCompiler, { CompilerError, CompilerErrorDetail, Effect, ErrorSeverity, parseConfigPragmaForTests, ValueKind, - runPlayground, type Hook, - findDirectiveDisablingMemoization, - findDirectiveEnablingMemoization, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, } from 'babel-plugin-react-compiler/src'; -import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment'; import clsx from 'clsx'; import invariant from 'invariant'; import {useSnackbar} from 'notistack'; @@ -39,32 +38,18 @@ import {useStore, useStoreDispatch} from '../StoreContext'; import Input from './Input'; import { CompilerOutput, + CompilerTransformOutput, default as Output, PrintedCompilerPipelineValue, } from './Output'; import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; +import {transformFromAstSync} from '@babel/core'; -type FunctionLike = - | NodePath - | NodePath - | NodePath; -enum MemoizeDirectiveState { - Enabled = 'Enabled', - Disabled = 'Disabled', - Undefined = 'Undefined', -} - -const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Undefined, -]); - -const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Disabled, -]); -function parseInput(input: string, language: 'flow' | 'typescript'): any { +function parseInput( + input: string, + language: 'flow' | 'typescript', +): ParseResult { // Extract the first line to quickly check for custom test directives if (language === 'flow') { return HermesParser.parse(input, { @@ -77,95 +62,45 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { return babelParse(input, { plugins: ['typescript', 'jsx'], sourceType: 'module', - }); + }) as ParseResult; } } -function parseFunctions( +function invokeCompiler( source: string, language: 'flow' | 'typescript', -): Array<{ - compilationEnabled: boolean; - fn: FunctionLike; -}> { - const items: Array<{ - compilationEnabled: boolean; - fn: FunctionLike; - }> = []; - try { - const ast = parseInput(source, language); - traverse(ast, { - FunctionDeclaration(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - ArrowFunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - FunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - }); - } catch (e) { - console.error(e); - CompilerError.throwInvalidJS({ - reason: String(e), - description: null, - loc: null, - suggestions: null, - }); + environment: EnvironmentConfig, + logIR: (pipelineValue: CompilerPipelineValue) => void, +): CompilerTransformOutput { + const opts: PluginOptions = parsePluginOptions({ + logger: { + debugLogIRs: logIR, + logEvent: () => {}, + }, + environment, + compilationMode: 'all', + panicThreshold: 'all_errors', + }); + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [[BabelPluginReactCompiler, opts]], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error('Expected successful compilation'); } - - return items; -} - -function shouldCompile(fn: FunctionLike): boolean { - const {body} = fn.node; - if (t.isBlockStatement(body)) { - const selfCheck = checkExplicitMemoizeDirectives(body.directives); - if (selfCheck === MemoizeDirectiveState.Enabled) return true; - if (selfCheck === MemoizeDirectiveState.Disabled) return false; - - const parentWithDirective = fn.findParent(parentPath => { - if (parentPath.isBlockStatement() || parentPath.isProgram()) { - const directiveCheck = checkExplicitMemoizeDirectives( - parentPath.node.directives, - ); - return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck); - } - return false; - }); - - if (!parentWithDirective) return true; - const parentDirectiveCheck = checkExplicitMemoizeDirectives( - (parentWithDirective.node as t.Program | t.BlockStatement).directives, - ); - return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); - } - return false; -} - -function checkExplicitMemoizeDirectives( - directives: Array, -): MemoizeDirectiveState { - if (findDirectiveEnablingMemoization(directives).length) { - return MemoizeDirectiveState.Enabled; - } - if (findDirectiveDisablingMemoization(directives).length) { - return MemoizeDirectiveState.Disabled; - } - return MemoizeDirectiveState.Undefined; + return { + code: result.code, + sourceMaps: result.map, + language, + }; } const COMMON_HOOKS: Array<[string, Hook]> = [ @@ -216,37 +151,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ ], ]; -function isHookName(s: string): boolean { - return /^use[A-Z0-9]/.test(s); -} - -function getReactFunctionType(id: t.Identifier | null): ReactFunctionType { - if (id != null) { - if (isHookName(id.name)) { - return 'Hook'; - } - - const isPascalCaseNameSpace = /^[A-Z].*/; - if (isPascalCaseNameSpace.test(id.name)) { - return 'Component'; - } - } - return 'Other'; -} - -function getFunctionIdentifier( - fn: - | NodePath - | NodePath - | NodePath, -): t.Identifier | null { - if (fn.isArrowFunctionExpression()) { - return null; - } - const id = fn.get('id'); - return Array.isArray(id) === false && id.isIdentifier() ? id.node : null; -} - function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map>(); const error = new CompilerError(); @@ -264,71 +168,25 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { } else { language = 'typescript'; } - let count = 0; - const withIdentifier = (id: t.Identifier | null): t.Identifier => { - if (id != null && id.name != null) { - return id; - } else { - return t.identifier(`anonymous_${count++}`); - } - }; + let transformOutput; try { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); const config = parseConfigPragmaForTests(pragma); - const parsedFunctions = parseFunctions(source, language); - for (const func of parsedFunctions) { - const id = withIdentifier(getFunctionIdentifier(func.fn)); - const fnName = id.name; - if (!func.compilationEnabled) { - upsert({ - kind: 'ast', - fnName, - name: 'CodeGen', - value: { - type: 'FunctionDeclaration', - id: - func.fn.isArrowFunctionExpression() || - func.fn.isFunctionExpression() - ? withIdentifier(null) - : func.fn.node.id, - async: func.fn.node.async, - generator: !!func.fn.node.generator, - body: func.fn.node.body as t.BlockStatement, - params: func.fn.node.params, - }, - }); - continue; - } - for (const result of runPlayground( - func.fn, - { - ...config, - customHooks: new Map([...COMMON_HOOKS]), - }, - getReactFunctionType(id), - )) { + + transformOutput = invokeCompiler( + source, + language, + {...config, customHooks: new Map([...COMMON_HOOKS])}, + result => { switch (result.kind) { case 'ast': { - upsert({ - kind: 'ast', - fnName, - name: result.name, - value: { - type: 'FunctionDeclaration', - id: withIdentifier(result.value.id), - async: result.value.async, - generator: result.value.generator, - body: result.value.body, - params: result.value.params, - }, - }); break; } case 'hir': { upsert({ kind: 'hir', - fnName, + fnName: result.value.id, name: result.name, value: printFunctionWithOutlined(result.value), }); @@ -337,7 +195,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'reactive': { upsert({ kind: 'reactive', - fnName, + fnName: result.value.id, name: result.name, value: printReactiveFunctionWithOutlined(result.value), }); @@ -346,7 +204,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'debug': { upsert({ kind: 'debug', - fnName, + fnName: null, name: result.name, value: result.value, }); @@ -357,8 +215,8 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { throw new Error(`Unhandled result ${result}`); } } - } - } + }, + ); } catch (err) { /** * error might be an invariant violation or other runtime error @@ -385,7 +243,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { if (error.hasErrors()) { return [{kind: 'err', results, error: error}, language]; } - return [{kind: 'ok', results}, language]; + return [{kind: 'ok', results, transformOutput}, language]; } export default function Editor(): JSX.Element { @@ -405,7 +263,7 @@ export default function Editor(): JSX.Element { } catch (e) { invariant(e instanceof Error, 'Only Error may be caught.'); enqueueSnackbar(e.message, { - variant: 'message', + variant: 'warning', ...createMessage( 'Bad URL - fell back to the default Playground.', MessageLevel.Info, diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 97a63400d2..d4127c63cf 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -5,8 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import generate from '@babel/generator'; -import * as t from '@babel/types'; import { CodeIcon, DocumentAddIcon, @@ -21,17 +19,12 @@ import {memo, ReactNode, useEffect, useState} from 'react'; import {type Store} from '../../lib/stores'; import TabbedWindow from '../TabbedWindow'; import {monacoOptions} from './monacoOptions'; +import {BabelFileResult} from '@babel/core'; const MemoizedOutput = memo(Output); export default MemoizedOutput; export type PrintedCompilerPipelineValue = - | { - kind: 'ast'; - name: string; - fnName: string | null; - value: t.FunctionDeclaration; - } | { kind: 'hir'; name: string; @@ -41,8 +34,17 @@ export type PrintedCompilerPipelineValue = | {kind: 'reactive'; name: string; fnName: string | null; value: string} | {kind: 'debug'; name: string; fnName: string | null; value: string}; +export type CompilerTransformOutput = { + code: string; + sourceMaps: BabelFileResult['map']; + language: 'flow' | 'typescript'; +}; export type CompilerOutput = - | {kind: 'ok'; results: Map>} + | { + kind: 'ok'; + transformOutput: CompilerTransformOutput; + results: Map>; + } | { kind: 'err'; results: Map>; @@ -61,7 +63,6 @@ async function tabify( const tabs = new Map(); const reorderedTabs = new Map(); const concattedResults = new Map(); - let topLevelFnDecls: Array = []; // Concat all top level function declaration results into a single tab for each pass for (const [passName, results] of compilerOutput.results) { for (const result of results) { @@ -87,9 +88,6 @@ async function tabify( } break; } - case 'ast': - topLevelFnDecls.push(result.value); - break; case 'debug': { concattedResults.set(passName, result.value); break; @@ -114,13 +112,17 @@ async function tabify( lastPassOutput = text; } // Ensure that JS and the JS source map come first - if (topLevelFnDecls.length > 0) { - /** - * Make a synthetic Program so we can have a single AST with all the top level - * FunctionDeclarations - */ - const ast = t.program(topLevelFnDecls); - const {code, sourceMapUrl} = await codegen(ast, source); + if (compilerOutput.kind === 'ok') { + const {transformOutput} = compilerOutput; + const sourceMapUrl = getSourceMapUrl( + transformOutput.code, + JSON.stringify(transformOutput.sourceMaps), + ); + const code = await prettier.format(transformOutput.code, { + semi: true, + parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts', + plugins: [parserBabel, prettierPluginEstree], + }); reorderedTabs.set( 'JS', { - const generated = generate( - ast, - {sourceMaps: true, sourceFileName: 'input.js'}, - source, - ); - const sourceMapUrl = getSourceMapUrl( - generated.code, - JSON.stringify(generated.map), - ); - const codegenOutput = await prettier.format(generated.code, { - semi: true, - parser: 'babel', - plugins: [parserBabel, prettierPluginEstree], - }); - return {code: codegenOutput, sourceMapUrl}; -} - function utf16ToUTF8(s: string): string { return unescape(encodeURIComponent(s)); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts index 401cbd4bdf..c648c66043 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -39,7 +39,10 @@ export default function BabelPluginReactCompiler( ) { opts = injectReanimatedFlag(opts); } - if (isDev) { + if ( + opts.environment.enableResetCacheOnSourceFileChanges !== false && + isDev + ) { opts = { ...opts, environment: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 72ed9e7c86..fb951d25c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -15,6 +15,7 @@ import { } from '../HIR/Environment'; import {hasOwnProperty} from '../Utils/utils'; import {fromZodError} from 'zod-validation-error'; +import {CompilerPipelineValue} from './Pipeline'; const PanicThresholdOptionsSchema = z.enum([ /* @@ -209,6 +210,7 @@ export type LoggerEvent = export type Logger = { logEvent: (filename: string | null, event: LoggerEvent) => void; + debugLogIRs?: (value: CompilerPipelineValue) => void; }; export const defaultOptions: PluginOptions = { 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 8a97eea217..c3179dd638 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -112,7 +112,7 @@ export type CompilerPipelineValue = | {kind: 'reactive'; name: string; value: ReactiveFunction} | {kind: 'debug'; name: string; value: string}; -export function* run( +function run( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, @@ -122,7 +122,7 @@ export function* run( logger: Logger | null, filename: string | null, code: string | null, -): Generator { +): CodegenFunction { const contextIdentifiers = findContextIdentifiers(func); const env = new Environment( func.scope, @@ -134,12 +134,17 @@ export function* run( code, useMemoCacheIdentifier, ); - yield log({ + env.logger?.debugLogIRs?.({ kind: 'debug', name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - const ast = yield* runWithEnvironment(func, env); + printLog({ + kind: 'debug', + name: 'EnvironmentConfig', + value: prettyFormat(env.config), + }); + const ast = runWithEnvironment(func, env); return ast; } @@ -147,17 +152,22 @@ export function* run( * Note: this is split from run() to make `config` out of scope, so that all * access to feature flags has to be through the Environment for consistency. */ -function* runWithEnvironment( +function runWithEnvironment( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, env: Environment, -): Generator { +): CodegenFunction { + const log = (value: CompilerPipelineValue): CompilerPipelineValue => { + printLog(value); + env.logger?.debugLogIRs?.(value); + return value; + }; const hir = lower(func, env).unwrap(); - yield log({kind: 'hir', name: 'HIR', value: hir}); + log({kind: 'hir', name: 'HIR', value: hir}); pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); validateContextVariableLValues(hir); validateUseMemo(hir); @@ -168,35 +178,35 @@ function* runWithEnvironment( !env.config.enableChangeDetectionForDebugging ) { dropManualMemoization(hir); - yield log({kind: 'hir', name: 'DropManualMemoization', value: hir}); + log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } inlineImmediatelyInvokedFunctionExpressions(hir); - yield log({ + log({ kind: 'hir', name: 'InlineImmediatelyInvokedFunctionExpressions', value: hir, }); mergeConsecutiveBlocks(hir); - yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); + log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); assertConsistentIdentifiers(hir); assertTerminalSuccessorsExist(hir); enterSSA(hir); - yield log({kind: 'hir', name: 'SSA', value: hir}); + log({kind: 'hir', name: 'SSA', value: hir}); eliminateRedundantPhi(hir); - yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); + log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); assertConsistentIdentifiers(hir); constantPropagation(hir); - yield log({kind: 'hir', name: 'ConstantPropagation', value: hir}); + log({kind: 'hir', name: 'ConstantPropagation', value: hir}); inferTypes(hir); - yield log({kind: 'hir', name: 'InferTypes', value: hir}); + log({kind: 'hir', name: 'InferTypes', value: hir}); if (env.config.validateHooksUsage) { validateHooksUsage(hir); @@ -214,27 +224,27 @@ function* runWithEnvironment( yield log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir}); analyseFunctions(hir); - yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); + log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); inferReferenceEffects(hir); - yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); + log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); validateLocalsNotReassignedAfterRender(hir); // Note: Has to come after infer reference effects because "dead" code may still affect inference deadCodeElimination(hir); - yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); + log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); if (env.config.enableInstructionReordering) { instructionReordering(hir); - yield log({kind: 'hir', name: 'InstructionReordering', value: hir}); + log({kind: 'hir', name: 'InstructionReordering', value: hir}); } pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); inferMutableRanges(hir); - yield log({kind: 'hir', name: 'InferMutableRanges', value: hir}); + log({kind: 'hir', name: 'InferMutableRanges', value: hir}); if (env.config.assertValidMutableRanges) { assertValidMutableRanges(hir); @@ -257,27 +267,27 @@ function* runWithEnvironment( } inferReactivePlaces(hir); - yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); + log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); rewriteInstructionKindsBasedOnReassignment(hir); - yield log({ + log({ kind: 'hir', name: 'RewriteInstructionKindsBasedOnReassignment', value: hir, }); propagatePhiTypes(hir); - yield log({ + log({ kind: 'hir', name: 'PropagatePhiTypes', value: hir, }); inferReactiveScopeVariables(hir); - yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); + log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir); - yield log({ + log({ kind: 'hir', name: 'MemoizeFbtAndMacroOperandsInSameScope', value: hir, @@ -289,39 +299,39 @@ function* runWithEnvironment( if (env.config.enableFunctionOutlining) { outlineFunctions(hir, fbtOperands); - yield log({kind: 'hir', name: 'OutlineFunctions', value: hir}); + log({kind: 'hir', name: 'OutlineFunctions', value: hir}); } alignMethodCallScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignMethodCallScopes', value: hir, }); alignObjectMethodScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignObjectMethodScopes', value: hir, }); pruneUnusedLabelsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PruneUnusedLabelsHIR', value: hir, }); alignReactiveScopesToBlockScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'AlignReactiveScopesToBlockScopesHIR', value: hir, }); mergeOverlappingReactiveScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'MergeOverlappingReactiveScopesHIR', value: hir, @@ -329,7 +339,7 @@ function* runWithEnvironment( assertValidBlockNesting(hir); buildReactiveScopeTerminalsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'BuildReactiveScopeTerminalsHIR', value: hir, @@ -338,14 +348,14 @@ function* runWithEnvironment( assertValidBlockNesting(hir); flattenReactiveLoopsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenReactiveLoopsHIR', value: hir, }); flattenScopesWithHooksOrUseHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenScopesWithHooksOrUseHIR', value: hir, @@ -353,7 +363,7 @@ function* runWithEnvironment( assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); propagateScopeDependenciesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PropagateScopeDependenciesHIR', value: hir, @@ -365,7 +375,7 @@ function* runWithEnvironment( if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); - yield log({ + log({ kind: 'hir', name: 'inlineJsxTransform', value: hir, @@ -373,7 +383,7 @@ function* runWithEnvironment( } const reactiveFunction = buildReactiveFunction(hir); - yield log({ + log({ kind: 'reactive', name: 'BuildReactiveFunction', value: reactiveFunction, @@ -382,7 +392,7 @@ function* runWithEnvironment( assertWellFormedBreakTargets(reactiveFunction); pruneUnusedLabels(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLabels', value: reactiveFunction, @@ -390,35 +400,35 @@ function* runWithEnvironment( assertScopeInstructionsWithinScopes(reactiveFunction); pruneNonEscapingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonEscapingScopes', value: reactiveFunction, }); pruneNonReactiveDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonReactiveDependencies', value: reactiveFunction, }); pruneUnusedScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedScopes', value: reactiveFunction, }); mergeReactiveScopesThatInvalidateTogether(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'MergeReactiveScopesThatInvalidateTogether', value: reactiveFunction, }); pruneAlwaysInvalidatingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneAlwaysInvalidatingScopes', value: reactiveFunction, @@ -426,7 +436,7 @@ function* runWithEnvironment( if (env.config.enableChangeDetectionForDebugging != null) { pruneInitializationDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneInitializationDependencies', value: reactiveFunction, @@ -434,49 +444,49 @@ function* runWithEnvironment( } propagateEarlyReturns(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PropagateEarlyReturns', value: reactiveFunction, }); pruneUnusedLValues(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLValues', value: reactiveFunction, }); promoteUsedTemporaries(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PromoteUsedTemporaries', value: reactiveFunction, }); extractScopeDeclarationsFromDestructuring(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'ExtractScopeDeclarationsFromDestructuring', value: reactiveFunction, }); stabilizeBlockIds(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'StabilizeBlockIds', value: reactiveFunction, }); const uniqueIdentifiers = renameVariables(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'RenameVariables', value: reactiveFunction, }); pruneHoistedContexts(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneHoistedContexts', value: reactiveFunction, @@ -497,9 +507,9 @@ function* runWithEnvironment( uniqueIdentifiers, fbtOperands, }).unwrap(); - yield log({kind: 'ast', name: 'Codegen', value: ast}); + log({kind: 'ast', name: 'Codegen', value: ast}); for (const outlined of ast.outlined) { - yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); + log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); } /** @@ -525,7 +535,7 @@ export function compileFn( filename: string | null, code: string | null, ): CodegenFunction { - let generator = run( + return run( func, config, fnType, @@ -534,15 +544,9 @@ export function compileFn( filename, code, ); - while (true) { - const next = generator.next(); - if (next.done) { - return next.value; - } - } } -export function log(value: CompilerPipelineValue): CompilerPipelineValue { +function printLog(value: CompilerPipelineValue): CompilerPipelineValue { switch (value.kind) { case 'ast': { logCodegenFunction(value.name, value.value); @@ -566,14 +570,3 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue { } return value; } - -export function* runPlayground( - func: NodePath< - t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression - >, - config: EnvironmentConfig, - fnType: ReactFunctionType, -): Generator { - const ast = yield* run(func, config, fnType, '_c', null, null, null); - return ast; -} 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 fa581d8ed8..48589b8be9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -168,11 +168,19 @@ const EnvironmentConfigSchema = z.object({ customMacros: z.nullable(z.array(MacroSchema)).default(null), /** - * Enable a check that resets the memoization cache when the source code of the file changes. - * This is intended to support hot module reloading (HMR), where the same runtime component - * instance will be reused across different versions of the component source. + * Enable a check that resets the memoization cache when the source code of + * the file changes. This is intended to support hot module reloading (HMR), + * where the same runtime component instance will be reused across different + * versions of the component source. + * + * When set to + * - true: code for HMR support is always generated, regardless of NODE_ENV + * or `globalThis.__DEV__` + * - false: code for HMR support is not generated + * - null: (default) code for HMR support is conditionally generated dependent + * on `NODE_ENV` and `globalThis.__DEV__` at the time of compilation. */ - enableResetCacheOnSourceFileChanges: z.boolean().default(false), + enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null), /** * Enable using information from existing useMemo/useCallback to understand when a value is done @@ -708,7 +716,10 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { continue; } - if (typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean') { + if ( + key !== 'enableResetCacheOnSourceFileChanges' && + typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean' + ) { // skip parsing non-boolean properties continue; } @@ -718,9 +729,15 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { maybeConfig[key] = false; } } - const config = EnvironmentConfigSchema.safeParse(maybeConfig); if (config.success) { + /** + * Unless explicitly enabled, do not insert HMR handling code + * in test fixtures or playground to reduce visual noise. + */ + if (config.data.enableResetCacheOnSourceFileChanges == null) { + config.data.enableResetCacheOnSourceFileChanges = false; + } return config.data; } CompilerError.invariant(false, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md new file mode 100644 index 0000000000..69c1b9bbbb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -0,0 +1,29 @@ + +## Input + +```javascript +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} + +``` + +## Code + +```javascript +// @compilationMode(all) +"use no memo"; + +function TestComponent({ x }) { + "use memo"; + return ; +} + +``` + +### 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/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js new file mode 100644 index 0000000000..9b314e1f99 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -0,0 +1,7 @@ +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 150df26e45..188c244d9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -17,8 +17,6 @@ export { compileFn as compile, compileProgram, parsePluginOptions, - run, - runPlayground, OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, findDirectiveEnablingMemoization, From 8b1ea5678aa1a3dff0f6c16f69d7c164f96bd481 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 12:06:48 -0500 Subject: [PATCH 124/422] [compiler][be] Playground now compiles entire program Compiler playground now runs the entire program through `babel-plugin-react-compiler` instead of a custom pipeline which previously duplicated function inference logic from `Program.ts`. In addition, the playground output reflects the tranformed file (instead of a "virtual file" of manually concatenated functions). This helps with the following: - Reduce potential discrepencies between playground and babel plugin behavior. See attached fixture output for an example where we previously diverged. - Let playground users see compiler-inserted imports (e.g. `_c` or `useFire`) This also helps us repurpose playground into a more general tool for compiler-users instead of just for compiler engineers. - imports and other functions are preserved. We differentiate between imports and globals in many cases (e.g. `inferEffectDeps`), so it may be misleading to omit imports in printed output - playground now shows other program-changing behavior like position of outlined functions and hoisted declarations - emitted compiled functions do not need synthetic names --- .../page.spec.ts/01-user-output.txt | 3 +- .../page.spec.ts/02-default-output.txt | 3 +- .../module-scope-use-memo-output.txt | 4 +- .../module-scope-use-no-memo-output.txt | 3 +- ...cope-does-not-beat-module-scope-output.txt | 5 + .../page.spec.ts/use-memo-output.txt | 5 +- .../page.spec.ts/use-no-memo-output.txt | 8 +- .../playground/__tests__/e2e/page.spec.ts | 2 +- .../components/Editor/EditorImpl.tsx | 260 ++++-------------- .../playground/components/Editor/Output.tsx | 63 ++--- .../src/Babel/BabelPlugin.ts | 5 +- .../src/Entrypoint/Options.ts | 2 + .../src/Entrypoint/Pipeline.ts | 141 +++++----- .../src/HIR/Environment.ts | 29 +- ...ule-scope-usememo-function-scope.expect.md | 29 ++ ...emo-module-scope-usememo-function-scope.js | 7 + .../babel-plugin-react-compiler/src/index.ts | 2 - 17 files changed, 235 insertions(+), 336 deletions(-) create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt index ba680bbb57..1600f35107 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt @@ -1,4 +1,5 @@ -function TestComponent(t0) { +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt index 2cbd09bba6..1d59a120f9 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt @@ -1,4 +1,5 @@ -function MyApp() { +import { c as _c } from "react/compiler-runtime"; +export default function MyApp() { const $ = _c(1); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt index ba680bbb57..638a2bcd22 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt @@ -1,4 +1,6 @@ -function TestComponent(t0) { +"use memo"; +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt index 2c69ddc1d6..ebd2d2b046 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt @@ -1,3 +1,4 @@ -function TestComponent({ x }) { +"use no memo"; +export default function TestComponent({ x }) { return ; } diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt new file mode 100644 index 0000000000..325e6972e1 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt @@ -0,0 +1,5 @@ +"use no memo"; +function TestComponent({ x }) { + "use memo"; + return ; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt index 804bacab97..de6dd52680 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt @@ -1,3 +1,4 @@ +import { c as _c } from "react/compiler-runtime"; function TestComponent(t0) { "use memo"; const $ = _c(2); @@ -12,7 +13,7 @@ function TestComponent(t0) { } return t1; } -function anonymous_1(t0) { +const TestComponent2 = (t0) => { "use memo"; const $ = _c(2); const { x } = t0; @@ -25,4 +26,4 @@ function anonymous_1(t0) { t1 = $[1]; } return t1; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt index 5fb66309fc..02c1367622 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt @@ -1,8 +1,8 @@ -function anonymous_1() { +const TestComponent = function () { "use no memo"; return ; -} -function anonymous_3({ x }) { +}; +const TestComponent2 = ({ x }) => { "use no memo"; return ; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 846e6227bd..254002c5ce 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -55,7 +55,7 @@ const TestComponent2 = ({ x }) => { };`, }, { - name: 'function-scope-beats-module-scope', + name: 'todo-function-scope-does-not-beat-module-scope', input: ` 'use no memo'; function TestComponent({ x }) { diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 82a40272bd..785b9fd075 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,23 +5,22 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse} from '@babel/parser'; +import {parse as babelParse, ParseResult} from '@babel/parser'; import * as HermesParser from 'hermes-parser'; -import traverse, {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import { +import BabelPluginReactCompiler, { CompilerError, CompilerErrorDetail, Effect, ErrorSeverity, parseConfigPragmaForTests, ValueKind, - runPlayground, type Hook, - findDirectiveDisablingMemoization, - findDirectiveEnablingMemoization, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, } from 'babel-plugin-react-compiler/src'; -import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment'; import clsx from 'clsx'; import invariant from 'invariant'; import {useSnackbar} from 'notistack'; @@ -39,32 +38,18 @@ import {useStore, useStoreDispatch} from '../StoreContext'; import Input from './Input'; import { CompilerOutput, + CompilerTransformOutput, default as Output, PrintedCompilerPipelineValue, } from './Output'; import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; +import {transformFromAstSync} from '@babel/core'; -type FunctionLike = - | NodePath - | NodePath - | NodePath; -enum MemoizeDirectiveState { - Enabled = 'Enabled', - Disabled = 'Disabled', - Undefined = 'Undefined', -} - -const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Undefined, -]); - -const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Disabled, -]); -function parseInput(input: string, language: 'flow' | 'typescript'): any { +function parseInput( + input: string, + language: 'flow' | 'typescript', +): ParseResult { // Extract the first line to quickly check for custom test directives if (language === 'flow') { return HermesParser.parse(input, { @@ -77,95 +62,45 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { return babelParse(input, { plugins: ['typescript', 'jsx'], sourceType: 'module', - }); + }) as ParseResult; } } -function parseFunctions( +function invokeCompiler( source: string, language: 'flow' | 'typescript', -): Array<{ - compilationEnabled: boolean; - fn: FunctionLike; -}> { - const items: Array<{ - compilationEnabled: boolean; - fn: FunctionLike; - }> = []; - try { - const ast = parseInput(source, language); - traverse(ast, { - FunctionDeclaration(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - ArrowFunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - FunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - }); - } catch (e) { - console.error(e); - CompilerError.throwInvalidJS({ - reason: String(e), - description: null, - loc: null, - suggestions: null, - }); + environment: EnvironmentConfig, + logIR: (pipelineValue: CompilerPipelineValue) => void, +): CompilerTransformOutput { + const opts: PluginOptions = parsePluginOptions({ + logger: { + debugLogIRs: logIR, + logEvent: () => {}, + }, + environment, + compilationMode: 'all', + panicThreshold: 'all_errors', + }); + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [[BabelPluginReactCompiler, opts]], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error('Expected successful compilation'); } - - return items; -} - -function shouldCompile(fn: FunctionLike): boolean { - const {body} = fn.node; - if (t.isBlockStatement(body)) { - const selfCheck = checkExplicitMemoizeDirectives(body.directives); - if (selfCheck === MemoizeDirectiveState.Enabled) return true; - if (selfCheck === MemoizeDirectiveState.Disabled) return false; - - const parentWithDirective = fn.findParent(parentPath => { - if (parentPath.isBlockStatement() || parentPath.isProgram()) { - const directiveCheck = checkExplicitMemoizeDirectives( - parentPath.node.directives, - ); - return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck); - } - return false; - }); - - if (!parentWithDirective) return true; - const parentDirectiveCheck = checkExplicitMemoizeDirectives( - (parentWithDirective.node as t.Program | t.BlockStatement).directives, - ); - return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); - } - return false; -} - -function checkExplicitMemoizeDirectives( - directives: Array, -): MemoizeDirectiveState { - if (findDirectiveEnablingMemoization(directives).length) { - return MemoizeDirectiveState.Enabled; - } - if (findDirectiveDisablingMemoization(directives).length) { - return MemoizeDirectiveState.Disabled; - } - return MemoizeDirectiveState.Undefined; + return { + code: result.code, + sourceMaps: result.map, + language, + }; } const COMMON_HOOKS: Array<[string, Hook]> = [ @@ -216,37 +151,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ ], ]; -function isHookName(s: string): boolean { - return /^use[A-Z0-9]/.test(s); -} - -function getReactFunctionType(id: t.Identifier | null): ReactFunctionType { - if (id != null) { - if (isHookName(id.name)) { - return 'Hook'; - } - - const isPascalCaseNameSpace = /^[A-Z].*/; - if (isPascalCaseNameSpace.test(id.name)) { - return 'Component'; - } - } - return 'Other'; -} - -function getFunctionIdentifier( - fn: - | NodePath - | NodePath - | NodePath, -): t.Identifier | null { - if (fn.isArrowFunctionExpression()) { - return null; - } - const id = fn.get('id'); - return Array.isArray(id) === false && id.isIdentifier() ? id.node : null; -} - function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map>(); const error = new CompilerError(); @@ -264,71 +168,25 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { } else { language = 'typescript'; } - let count = 0; - const withIdentifier = (id: t.Identifier | null): t.Identifier => { - if (id != null && id.name != null) { - return id; - } else { - return t.identifier(`anonymous_${count++}`); - } - }; + let transformOutput; try { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); const config = parseConfigPragmaForTests(pragma); - const parsedFunctions = parseFunctions(source, language); - for (const func of parsedFunctions) { - const id = withIdentifier(getFunctionIdentifier(func.fn)); - const fnName = id.name; - if (!func.compilationEnabled) { - upsert({ - kind: 'ast', - fnName, - name: 'CodeGen', - value: { - type: 'FunctionDeclaration', - id: - func.fn.isArrowFunctionExpression() || - func.fn.isFunctionExpression() - ? withIdentifier(null) - : func.fn.node.id, - async: func.fn.node.async, - generator: !!func.fn.node.generator, - body: func.fn.node.body as t.BlockStatement, - params: func.fn.node.params, - }, - }); - continue; - } - for (const result of runPlayground( - func.fn, - { - ...config, - customHooks: new Map([...COMMON_HOOKS]), - }, - getReactFunctionType(id), - )) { + + transformOutput = invokeCompiler( + source, + language, + {...config, customHooks: new Map([...COMMON_HOOKS])}, + result => { switch (result.kind) { case 'ast': { - upsert({ - kind: 'ast', - fnName, - name: result.name, - value: { - type: 'FunctionDeclaration', - id: withIdentifier(result.value.id), - async: result.value.async, - generator: result.value.generator, - body: result.value.body, - params: result.value.params, - }, - }); break; } case 'hir': { upsert({ kind: 'hir', - fnName, + fnName: result.value.id, name: result.name, value: printFunctionWithOutlined(result.value), }); @@ -337,7 +195,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'reactive': { upsert({ kind: 'reactive', - fnName, + fnName: result.value.id, name: result.name, value: printReactiveFunctionWithOutlined(result.value), }); @@ -346,7 +204,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'debug': { upsert({ kind: 'debug', - fnName, + fnName: null, name: result.name, value: result.value, }); @@ -357,8 +215,8 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { throw new Error(`Unhandled result ${result}`); } } - } - } + }, + ); } catch (err) { /** * error might be an invariant violation or other runtime error @@ -385,7 +243,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { if (error.hasErrors()) { return [{kind: 'err', results, error: error}, language]; } - return [{kind: 'ok', results}, language]; + return [{kind: 'ok', results, transformOutput}, language]; } export default function Editor(): JSX.Element { @@ -405,7 +263,7 @@ export default function Editor(): JSX.Element { } catch (e) { invariant(e instanceof Error, 'Only Error may be caught.'); enqueueSnackbar(e.message, { - variant: 'message', + variant: 'warning', ...createMessage( 'Bad URL - fell back to the default Playground.', MessageLevel.Info, diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 97a63400d2..d4127c63cf 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -5,8 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import generate from '@babel/generator'; -import * as t from '@babel/types'; import { CodeIcon, DocumentAddIcon, @@ -21,17 +19,12 @@ import {memo, ReactNode, useEffect, useState} from 'react'; import {type Store} from '../../lib/stores'; import TabbedWindow from '../TabbedWindow'; import {monacoOptions} from './monacoOptions'; +import {BabelFileResult} from '@babel/core'; const MemoizedOutput = memo(Output); export default MemoizedOutput; export type PrintedCompilerPipelineValue = - | { - kind: 'ast'; - name: string; - fnName: string | null; - value: t.FunctionDeclaration; - } | { kind: 'hir'; name: string; @@ -41,8 +34,17 @@ export type PrintedCompilerPipelineValue = | {kind: 'reactive'; name: string; fnName: string | null; value: string} | {kind: 'debug'; name: string; fnName: string | null; value: string}; +export type CompilerTransformOutput = { + code: string; + sourceMaps: BabelFileResult['map']; + language: 'flow' | 'typescript'; +}; export type CompilerOutput = - | {kind: 'ok'; results: Map>} + | { + kind: 'ok'; + transformOutput: CompilerTransformOutput; + results: Map>; + } | { kind: 'err'; results: Map>; @@ -61,7 +63,6 @@ async function tabify( const tabs = new Map(); const reorderedTabs = new Map(); const concattedResults = new Map(); - let topLevelFnDecls: Array = []; // Concat all top level function declaration results into a single tab for each pass for (const [passName, results] of compilerOutput.results) { for (const result of results) { @@ -87,9 +88,6 @@ async function tabify( } break; } - case 'ast': - topLevelFnDecls.push(result.value); - break; case 'debug': { concattedResults.set(passName, result.value); break; @@ -114,13 +112,17 @@ async function tabify( lastPassOutput = text; } // Ensure that JS and the JS source map come first - if (topLevelFnDecls.length > 0) { - /** - * Make a synthetic Program so we can have a single AST with all the top level - * FunctionDeclarations - */ - const ast = t.program(topLevelFnDecls); - const {code, sourceMapUrl} = await codegen(ast, source); + if (compilerOutput.kind === 'ok') { + const {transformOutput} = compilerOutput; + const sourceMapUrl = getSourceMapUrl( + transformOutput.code, + JSON.stringify(transformOutput.sourceMaps), + ); + const code = await prettier.format(transformOutput.code, { + semi: true, + parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts', + plugins: [parserBabel, prettierPluginEstree], + }); reorderedTabs.set( 'JS', { - const generated = generate( - ast, - {sourceMaps: true, sourceFileName: 'input.js'}, - source, - ); - const sourceMapUrl = getSourceMapUrl( - generated.code, - JSON.stringify(generated.map), - ); - const codegenOutput = await prettier.format(generated.code, { - semi: true, - parser: 'babel', - plugins: [parserBabel, prettierPluginEstree], - }); - return {code: codegenOutput, sourceMapUrl}; -} - function utf16ToUTF8(s: string): string { return unescape(encodeURIComponent(s)); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts index 401cbd4bdf..c648c66043 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -39,7 +39,10 @@ export default function BabelPluginReactCompiler( ) { opts = injectReanimatedFlag(opts); } - if (isDev) { + if ( + opts.environment.enableResetCacheOnSourceFileChanges !== false && + isDev + ) { opts = { ...opts, environment: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 72ed9e7c86..fb951d25c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -15,6 +15,7 @@ import { } from '../HIR/Environment'; import {hasOwnProperty} from '../Utils/utils'; import {fromZodError} from 'zod-validation-error'; +import {CompilerPipelineValue} from './Pipeline'; const PanicThresholdOptionsSchema = z.enum([ /* @@ -209,6 +210,7 @@ export type LoggerEvent = export type Logger = { logEvent: (filename: string | null, event: LoggerEvent) => void; + debugLogIRs?: (value: CompilerPipelineValue) => void; }; export const defaultOptions: PluginOptions = { 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 8a97eea217..6995aec7f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -112,7 +112,7 @@ export type CompilerPipelineValue = | {kind: 'reactive'; name: string; value: ReactiveFunction} | {kind: 'debug'; name: string; value: string}; -export function* run( +function run( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, @@ -122,7 +122,7 @@ export function* run( logger: Logger | null, filename: string | null, code: string | null, -): Generator { +): CodegenFunction { const contextIdentifiers = findContextIdentifiers(func); const env = new Environment( func.scope, @@ -134,12 +134,17 @@ export function* run( code, useMemoCacheIdentifier, ); - yield log({ + env.logger?.debugLogIRs?.({ kind: 'debug', name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - const ast = yield* runWithEnvironment(func, env); + printLog({ + kind: 'debug', + name: 'EnvironmentConfig', + value: prettyFormat(env.config), + }); + const ast = runWithEnvironment(func, env); return ast; } @@ -147,17 +152,22 @@ export function* run( * Note: this is split from run() to make `config` out of scope, so that all * access to feature flags has to be through the Environment for consistency. */ -function* runWithEnvironment( +function runWithEnvironment( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, env: Environment, -): Generator { +): CodegenFunction { + const log = (value: CompilerPipelineValue): CompilerPipelineValue => { + printLog(value); + env.logger?.debugLogIRs?.(value); + return value; + }; const hir = lower(func, env).unwrap(); - yield log({kind: 'hir', name: 'HIR', value: hir}); + log({kind: 'hir', name: 'HIR', value: hir}); pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); validateContextVariableLValues(hir); validateUseMemo(hir); @@ -168,35 +178,35 @@ function* runWithEnvironment( !env.config.enableChangeDetectionForDebugging ) { dropManualMemoization(hir); - yield log({kind: 'hir', name: 'DropManualMemoization', value: hir}); + log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } inlineImmediatelyInvokedFunctionExpressions(hir); - yield log({ + log({ kind: 'hir', name: 'InlineImmediatelyInvokedFunctionExpressions', value: hir, }); mergeConsecutiveBlocks(hir); - yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); + log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); assertConsistentIdentifiers(hir); assertTerminalSuccessorsExist(hir); enterSSA(hir); - yield log({kind: 'hir', name: 'SSA', value: hir}); + log({kind: 'hir', name: 'SSA', value: hir}); eliminateRedundantPhi(hir); - yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); + log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); assertConsistentIdentifiers(hir); constantPropagation(hir); - yield log({kind: 'hir', name: 'ConstantPropagation', value: hir}); + log({kind: 'hir', name: 'ConstantPropagation', value: hir}); inferTypes(hir); - yield log({kind: 'hir', name: 'InferTypes', value: hir}); + log({kind: 'hir', name: 'InferTypes', value: hir}); if (env.config.validateHooksUsage) { validateHooksUsage(hir); @@ -211,30 +221,30 @@ function* runWithEnvironment( } optimizePropsMethodCalls(hir); - yield log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir}); + log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir}); analyseFunctions(hir); - yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); + log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); inferReferenceEffects(hir); - yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); + log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); validateLocalsNotReassignedAfterRender(hir); // Note: Has to come after infer reference effects because "dead" code may still affect inference deadCodeElimination(hir); - yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); + log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); if (env.config.enableInstructionReordering) { instructionReordering(hir); - yield log({kind: 'hir', name: 'InstructionReordering', value: hir}); + log({kind: 'hir', name: 'InstructionReordering', value: hir}); } pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); inferMutableRanges(hir); - yield log({kind: 'hir', name: 'InferMutableRanges', value: hir}); + log({kind: 'hir', name: 'InferMutableRanges', value: hir}); if (env.config.assertValidMutableRanges) { assertValidMutableRanges(hir); @@ -257,27 +267,27 @@ function* runWithEnvironment( } inferReactivePlaces(hir); - yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); + log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); rewriteInstructionKindsBasedOnReassignment(hir); - yield log({ + log({ kind: 'hir', name: 'RewriteInstructionKindsBasedOnReassignment', value: hir, }); propagatePhiTypes(hir); - yield log({ + log({ kind: 'hir', name: 'PropagatePhiTypes', value: hir, }); inferReactiveScopeVariables(hir); - yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); + log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir); - yield log({ + log({ kind: 'hir', name: 'MemoizeFbtAndMacroOperandsInSameScope', value: hir, @@ -289,39 +299,39 @@ function* runWithEnvironment( if (env.config.enableFunctionOutlining) { outlineFunctions(hir, fbtOperands); - yield log({kind: 'hir', name: 'OutlineFunctions', value: hir}); + log({kind: 'hir', name: 'OutlineFunctions', value: hir}); } alignMethodCallScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignMethodCallScopes', value: hir, }); alignObjectMethodScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignObjectMethodScopes', value: hir, }); pruneUnusedLabelsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PruneUnusedLabelsHIR', value: hir, }); alignReactiveScopesToBlockScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'AlignReactiveScopesToBlockScopesHIR', value: hir, }); mergeOverlappingReactiveScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'MergeOverlappingReactiveScopesHIR', value: hir, @@ -329,7 +339,7 @@ function* runWithEnvironment( assertValidBlockNesting(hir); buildReactiveScopeTerminalsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'BuildReactiveScopeTerminalsHIR', value: hir, @@ -338,14 +348,14 @@ function* runWithEnvironment( assertValidBlockNesting(hir); flattenReactiveLoopsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenReactiveLoopsHIR', value: hir, }); flattenScopesWithHooksOrUseHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenScopesWithHooksOrUseHIR', value: hir, @@ -353,7 +363,7 @@ function* runWithEnvironment( assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); propagateScopeDependenciesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PropagateScopeDependenciesHIR', value: hir, @@ -365,7 +375,7 @@ function* runWithEnvironment( if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); - yield log({ + log({ kind: 'hir', name: 'inlineJsxTransform', value: hir, @@ -373,7 +383,7 @@ function* runWithEnvironment( } const reactiveFunction = buildReactiveFunction(hir); - yield log({ + log({ kind: 'reactive', name: 'BuildReactiveFunction', value: reactiveFunction, @@ -382,7 +392,7 @@ function* runWithEnvironment( assertWellFormedBreakTargets(reactiveFunction); pruneUnusedLabels(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLabels', value: reactiveFunction, @@ -390,35 +400,35 @@ function* runWithEnvironment( assertScopeInstructionsWithinScopes(reactiveFunction); pruneNonEscapingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonEscapingScopes', value: reactiveFunction, }); pruneNonReactiveDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonReactiveDependencies', value: reactiveFunction, }); pruneUnusedScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedScopes', value: reactiveFunction, }); mergeReactiveScopesThatInvalidateTogether(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'MergeReactiveScopesThatInvalidateTogether', value: reactiveFunction, }); pruneAlwaysInvalidatingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneAlwaysInvalidatingScopes', value: reactiveFunction, @@ -426,7 +436,7 @@ function* runWithEnvironment( if (env.config.enableChangeDetectionForDebugging != null) { pruneInitializationDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneInitializationDependencies', value: reactiveFunction, @@ -434,49 +444,49 @@ function* runWithEnvironment( } propagateEarlyReturns(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PropagateEarlyReturns', value: reactiveFunction, }); pruneUnusedLValues(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLValues', value: reactiveFunction, }); promoteUsedTemporaries(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PromoteUsedTemporaries', value: reactiveFunction, }); extractScopeDeclarationsFromDestructuring(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'ExtractScopeDeclarationsFromDestructuring', value: reactiveFunction, }); stabilizeBlockIds(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'StabilizeBlockIds', value: reactiveFunction, }); const uniqueIdentifiers = renameVariables(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'RenameVariables', value: reactiveFunction, }); pruneHoistedContexts(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneHoistedContexts', value: reactiveFunction, @@ -497,9 +507,9 @@ function* runWithEnvironment( uniqueIdentifiers, fbtOperands, }).unwrap(); - yield log({kind: 'ast', name: 'Codegen', value: ast}); + log({kind: 'ast', name: 'Codegen', value: ast}); for (const outlined of ast.outlined) { - yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); + log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); } /** @@ -525,7 +535,7 @@ export function compileFn( filename: string | null, code: string | null, ): CodegenFunction { - let generator = run( + return run( func, config, fnType, @@ -534,15 +544,9 @@ export function compileFn( filename, code, ); - while (true) { - const next = generator.next(); - if (next.done) { - return next.value; - } - } } -export function log(value: CompilerPipelineValue): CompilerPipelineValue { +function printLog(value: CompilerPipelineValue): CompilerPipelineValue { switch (value.kind) { case 'ast': { logCodegenFunction(value.name, value.value); @@ -566,14 +570,3 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue { } return value; } - -export function* runPlayground( - func: NodePath< - t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression - >, - config: EnvironmentConfig, - fnType: ReactFunctionType, -): Generator { - const ast = yield* run(func, config, fnType, '_c', null, null, null); - return ast; -} 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 fa581d8ed8..48589b8be9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -168,11 +168,19 @@ const EnvironmentConfigSchema = z.object({ customMacros: z.nullable(z.array(MacroSchema)).default(null), /** - * Enable a check that resets the memoization cache when the source code of the file changes. - * This is intended to support hot module reloading (HMR), where the same runtime component - * instance will be reused across different versions of the component source. + * Enable a check that resets the memoization cache when the source code of + * the file changes. This is intended to support hot module reloading (HMR), + * where the same runtime component instance will be reused across different + * versions of the component source. + * + * When set to + * - true: code for HMR support is always generated, regardless of NODE_ENV + * or `globalThis.__DEV__` + * - false: code for HMR support is not generated + * - null: (default) code for HMR support is conditionally generated dependent + * on `NODE_ENV` and `globalThis.__DEV__` at the time of compilation. */ - enableResetCacheOnSourceFileChanges: z.boolean().default(false), + enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null), /** * Enable using information from existing useMemo/useCallback to understand when a value is done @@ -708,7 +716,10 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { continue; } - if (typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean') { + if ( + key !== 'enableResetCacheOnSourceFileChanges' && + typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean' + ) { // skip parsing non-boolean properties continue; } @@ -718,9 +729,15 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { maybeConfig[key] = false; } } - const config = EnvironmentConfigSchema.safeParse(maybeConfig); if (config.success) { + /** + * Unless explicitly enabled, do not insert HMR handling code + * in test fixtures or playground to reduce visual noise. + */ + if (config.data.enableResetCacheOnSourceFileChanges == null) { + config.data.enableResetCacheOnSourceFileChanges = false; + } return config.data; } CompilerError.invariant(false, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md new file mode 100644 index 0000000000..69c1b9bbbb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -0,0 +1,29 @@ + +## Input + +```javascript +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} + +``` + +## Code + +```javascript +// @compilationMode(all) +"use no memo"; + +function TestComponent({ x }) { + "use memo"; + return ; +} + +``` + +### 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/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js new file mode 100644 index 0000000000..9b314e1f99 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -0,0 +1,7 @@ +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 150df26e45..188c244d9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -17,8 +17,6 @@ export { compileFn as compile, compileProgram, parsePluginOptions, - run, - runPlayground, OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, findDirectiveEnablingMemoization, From ce17de3d31d8e7e6c5f5987b95b4ed0ed8a22ae5 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 12:18:51 -0500 Subject: [PATCH 125/422] [compiler][be] Playground now compiles entire program Compiler playground now runs the entire program through `babel-plugin-react-compiler` instead of a custom pipeline which previously duplicated function inference logic from `Program.ts`. In addition, the playground output reflects the tranformed file (instead of a "virtual file" of manually concatenated functions). This helps with the following: - Reduce potential discrepencies between playground and babel plugin behavior. See attached fixture output for an example where we previously diverged. - Let playground users see compiler-inserted imports (e.g. `_c` or `useFire`) This also helps us repurpose playground into a more general tool for compiler-users instead of just for compiler engineers. - imports and other functions are preserved. We differentiate between imports and globals in many cases (e.g. `inferEffectDeps`), so it may be misleading to omit imports in printed output - playground now shows other program-changing behavior like position of outlined functions and hoisted declarations - emitted compiled functions do not need synthetic names --- .../page.spec.ts/01-user-output.txt | 3 +- .../page.spec.ts/02-default-output.txt | 3 +- .../module-scope-use-memo-output.txt | 4 +- .../module-scope-use-no-memo-output.txt | 3 +- ...cope-does-not-beat-module-scope-output.txt | 5 + .../page.spec.ts/use-memo-output.txt | 5 +- .../page.spec.ts/use-no-memo-output.txt | 8 +- .../playground/__tests__/e2e/page.spec.ts | 2 +- .../components/Editor/EditorImpl.tsx | 260 ++++-------------- .../playground/components/Editor/Output.tsx | 63 ++--- .../src/Babel/BabelPlugin.ts | 5 +- .../src/Entrypoint/Options.ts | 2 + .../src/Entrypoint/Pipeline.ts | 141 +++++----- .../src/HIR/Environment.ts | 29 +- ...ule-scope-usememo-function-scope.expect.md | 29 ++ ...emo-module-scope-usememo-function-scope.js | 7 + .../src/__tests__/parseConfigPragma-test.ts | 1 + .../babel-plugin-react-compiler/src/index.ts | 2 - 18 files changed, 236 insertions(+), 336 deletions(-) create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt index ba680bbb57..1600f35107 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt @@ -1,4 +1,5 @@ -function TestComponent(t0) { +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt index 2cbd09bba6..1d59a120f9 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt @@ -1,4 +1,5 @@ -function MyApp() { +import { c as _c } from "react/compiler-runtime"; +export default function MyApp() { const $ = _c(1); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt index ba680bbb57..638a2bcd22 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt @@ -1,4 +1,6 @@ -function TestComponent(t0) { +"use memo"; +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt index 2c69ddc1d6..ebd2d2b046 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt @@ -1,3 +1,4 @@ -function TestComponent({ x }) { +"use no memo"; +export default function TestComponent({ x }) { return ; } diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt new file mode 100644 index 0000000000..325e6972e1 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt @@ -0,0 +1,5 @@ +"use no memo"; +function TestComponent({ x }) { + "use memo"; + return ; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt index 804bacab97..de6dd52680 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt @@ -1,3 +1,4 @@ +import { c as _c } from "react/compiler-runtime"; function TestComponent(t0) { "use memo"; const $ = _c(2); @@ -12,7 +13,7 @@ function TestComponent(t0) { } return t1; } -function anonymous_1(t0) { +const TestComponent2 = (t0) => { "use memo"; const $ = _c(2); const { x } = t0; @@ -25,4 +26,4 @@ function anonymous_1(t0) { t1 = $[1]; } return t1; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt index 5fb66309fc..02c1367622 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt @@ -1,8 +1,8 @@ -function anonymous_1() { +const TestComponent = function () { "use no memo"; return ; -} -function anonymous_3({ x }) { +}; +const TestComponent2 = ({ x }) => { "use no memo"; return ; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 846e6227bd..254002c5ce 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -55,7 +55,7 @@ const TestComponent2 = ({ x }) => { };`, }, { - name: 'function-scope-beats-module-scope', + name: 'todo-function-scope-does-not-beat-module-scope', input: ` 'use no memo'; function TestComponent({ x }) { diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 82a40272bd..785b9fd075 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,23 +5,22 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse} from '@babel/parser'; +import {parse as babelParse, ParseResult} from '@babel/parser'; import * as HermesParser from 'hermes-parser'; -import traverse, {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import { +import BabelPluginReactCompiler, { CompilerError, CompilerErrorDetail, Effect, ErrorSeverity, parseConfigPragmaForTests, ValueKind, - runPlayground, type Hook, - findDirectiveDisablingMemoization, - findDirectiveEnablingMemoization, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, } from 'babel-plugin-react-compiler/src'; -import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment'; import clsx from 'clsx'; import invariant from 'invariant'; import {useSnackbar} from 'notistack'; @@ -39,32 +38,18 @@ import {useStore, useStoreDispatch} from '../StoreContext'; import Input from './Input'; import { CompilerOutput, + CompilerTransformOutput, default as Output, PrintedCompilerPipelineValue, } from './Output'; import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; +import {transformFromAstSync} from '@babel/core'; -type FunctionLike = - | NodePath - | NodePath - | NodePath; -enum MemoizeDirectiveState { - Enabled = 'Enabled', - Disabled = 'Disabled', - Undefined = 'Undefined', -} - -const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Undefined, -]); - -const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Disabled, -]); -function parseInput(input: string, language: 'flow' | 'typescript'): any { +function parseInput( + input: string, + language: 'flow' | 'typescript', +): ParseResult { // Extract the first line to quickly check for custom test directives if (language === 'flow') { return HermesParser.parse(input, { @@ -77,95 +62,45 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { return babelParse(input, { plugins: ['typescript', 'jsx'], sourceType: 'module', - }); + }) as ParseResult; } } -function parseFunctions( +function invokeCompiler( source: string, language: 'flow' | 'typescript', -): Array<{ - compilationEnabled: boolean; - fn: FunctionLike; -}> { - const items: Array<{ - compilationEnabled: boolean; - fn: FunctionLike; - }> = []; - try { - const ast = parseInput(source, language); - traverse(ast, { - FunctionDeclaration(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - ArrowFunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - FunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - }); - } catch (e) { - console.error(e); - CompilerError.throwInvalidJS({ - reason: String(e), - description: null, - loc: null, - suggestions: null, - }); + environment: EnvironmentConfig, + logIR: (pipelineValue: CompilerPipelineValue) => void, +): CompilerTransformOutput { + const opts: PluginOptions = parsePluginOptions({ + logger: { + debugLogIRs: logIR, + logEvent: () => {}, + }, + environment, + compilationMode: 'all', + panicThreshold: 'all_errors', + }); + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [[BabelPluginReactCompiler, opts]], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error('Expected successful compilation'); } - - return items; -} - -function shouldCompile(fn: FunctionLike): boolean { - const {body} = fn.node; - if (t.isBlockStatement(body)) { - const selfCheck = checkExplicitMemoizeDirectives(body.directives); - if (selfCheck === MemoizeDirectiveState.Enabled) return true; - if (selfCheck === MemoizeDirectiveState.Disabled) return false; - - const parentWithDirective = fn.findParent(parentPath => { - if (parentPath.isBlockStatement() || parentPath.isProgram()) { - const directiveCheck = checkExplicitMemoizeDirectives( - parentPath.node.directives, - ); - return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck); - } - return false; - }); - - if (!parentWithDirective) return true; - const parentDirectiveCheck = checkExplicitMemoizeDirectives( - (parentWithDirective.node as t.Program | t.BlockStatement).directives, - ); - return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); - } - return false; -} - -function checkExplicitMemoizeDirectives( - directives: Array, -): MemoizeDirectiveState { - if (findDirectiveEnablingMemoization(directives).length) { - return MemoizeDirectiveState.Enabled; - } - if (findDirectiveDisablingMemoization(directives).length) { - return MemoizeDirectiveState.Disabled; - } - return MemoizeDirectiveState.Undefined; + return { + code: result.code, + sourceMaps: result.map, + language, + }; } const COMMON_HOOKS: Array<[string, Hook]> = [ @@ -216,37 +151,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ ], ]; -function isHookName(s: string): boolean { - return /^use[A-Z0-9]/.test(s); -} - -function getReactFunctionType(id: t.Identifier | null): ReactFunctionType { - if (id != null) { - if (isHookName(id.name)) { - return 'Hook'; - } - - const isPascalCaseNameSpace = /^[A-Z].*/; - if (isPascalCaseNameSpace.test(id.name)) { - return 'Component'; - } - } - return 'Other'; -} - -function getFunctionIdentifier( - fn: - | NodePath - | NodePath - | NodePath, -): t.Identifier | null { - if (fn.isArrowFunctionExpression()) { - return null; - } - const id = fn.get('id'); - return Array.isArray(id) === false && id.isIdentifier() ? id.node : null; -} - function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map>(); const error = new CompilerError(); @@ -264,71 +168,25 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { } else { language = 'typescript'; } - let count = 0; - const withIdentifier = (id: t.Identifier | null): t.Identifier => { - if (id != null && id.name != null) { - return id; - } else { - return t.identifier(`anonymous_${count++}`); - } - }; + let transformOutput; try { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); const config = parseConfigPragmaForTests(pragma); - const parsedFunctions = parseFunctions(source, language); - for (const func of parsedFunctions) { - const id = withIdentifier(getFunctionIdentifier(func.fn)); - const fnName = id.name; - if (!func.compilationEnabled) { - upsert({ - kind: 'ast', - fnName, - name: 'CodeGen', - value: { - type: 'FunctionDeclaration', - id: - func.fn.isArrowFunctionExpression() || - func.fn.isFunctionExpression() - ? withIdentifier(null) - : func.fn.node.id, - async: func.fn.node.async, - generator: !!func.fn.node.generator, - body: func.fn.node.body as t.BlockStatement, - params: func.fn.node.params, - }, - }); - continue; - } - for (const result of runPlayground( - func.fn, - { - ...config, - customHooks: new Map([...COMMON_HOOKS]), - }, - getReactFunctionType(id), - )) { + + transformOutput = invokeCompiler( + source, + language, + {...config, customHooks: new Map([...COMMON_HOOKS])}, + result => { switch (result.kind) { case 'ast': { - upsert({ - kind: 'ast', - fnName, - name: result.name, - value: { - type: 'FunctionDeclaration', - id: withIdentifier(result.value.id), - async: result.value.async, - generator: result.value.generator, - body: result.value.body, - params: result.value.params, - }, - }); break; } case 'hir': { upsert({ kind: 'hir', - fnName, + fnName: result.value.id, name: result.name, value: printFunctionWithOutlined(result.value), }); @@ -337,7 +195,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'reactive': { upsert({ kind: 'reactive', - fnName, + fnName: result.value.id, name: result.name, value: printReactiveFunctionWithOutlined(result.value), }); @@ -346,7 +204,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'debug': { upsert({ kind: 'debug', - fnName, + fnName: null, name: result.name, value: result.value, }); @@ -357,8 +215,8 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { throw new Error(`Unhandled result ${result}`); } } - } - } + }, + ); } catch (err) { /** * error might be an invariant violation or other runtime error @@ -385,7 +243,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { if (error.hasErrors()) { return [{kind: 'err', results, error: error}, language]; } - return [{kind: 'ok', results}, language]; + return [{kind: 'ok', results, transformOutput}, language]; } export default function Editor(): JSX.Element { @@ -405,7 +263,7 @@ export default function Editor(): JSX.Element { } catch (e) { invariant(e instanceof Error, 'Only Error may be caught.'); enqueueSnackbar(e.message, { - variant: 'message', + variant: 'warning', ...createMessage( 'Bad URL - fell back to the default Playground.', MessageLevel.Info, diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 97a63400d2..d4127c63cf 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -5,8 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import generate from '@babel/generator'; -import * as t from '@babel/types'; import { CodeIcon, DocumentAddIcon, @@ -21,17 +19,12 @@ import {memo, ReactNode, useEffect, useState} from 'react'; import {type Store} from '../../lib/stores'; import TabbedWindow from '../TabbedWindow'; import {monacoOptions} from './monacoOptions'; +import {BabelFileResult} from '@babel/core'; const MemoizedOutput = memo(Output); export default MemoizedOutput; export type PrintedCompilerPipelineValue = - | { - kind: 'ast'; - name: string; - fnName: string | null; - value: t.FunctionDeclaration; - } | { kind: 'hir'; name: string; @@ -41,8 +34,17 @@ export type PrintedCompilerPipelineValue = | {kind: 'reactive'; name: string; fnName: string | null; value: string} | {kind: 'debug'; name: string; fnName: string | null; value: string}; +export type CompilerTransformOutput = { + code: string; + sourceMaps: BabelFileResult['map']; + language: 'flow' | 'typescript'; +}; export type CompilerOutput = - | {kind: 'ok'; results: Map>} + | { + kind: 'ok'; + transformOutput: CompilerTransformOutput; + results: Map>; + } | { kind: 'err'; results: Map>; @@ -61,7 +63,6 @@ async function tabify( const tabs = new Map(); const reorderedTabs = new Map(); const concattedResults = new Map(); - let topLevelFnDecls: Array = []; // Concat all top level function declaration results into a single tab for each pass for (const [passName, results] of compilerOutput.results) { for (const result of results) { @@ -87,9 +88,6 @@ async function tabify( } break; } - case 'ast': - topLevelFnDecls.push(result.value); - break; case 'debug': { concattedResults.set(passName, result.value); break; @@ -114,13 +112,17 @@ async function tabify( lastPassOutput = text; } // Ensure that JS and the JS source map come first - if (topLevelFnDecls.length > 0) { - /** - * Make a synthetic Program so we can have a single AST with all the top level - * FunctionDeclarations - */ - const ast = t.program(topLevelFnDecls); - const {code, sourceMapUrl} = await codegen(ast, source); + if (compilerOutput.kind === 'ok') { + const {transformOutput} = compilerOutput; + const sourceMapUrl = getSourceMapUrl( + transformOutput.code, + JSON.stringify(transformOutput.sourceMaps), + ); + const code = await prettier.format(transformOutput.code, { + semi: true, + parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts', + plugins: [parserBabel, prettierPluginEstree], + }); reorderedTabs.set( 'JS', { - const generated = generate( - ast, - {sourceMaps: true, sourceFileName: 'input.js'}, - source, - ); - const sourceMapUrl = getSourceMapUrl( - generated.code, - JSON.stringify(generated.map), - ); - const codegenOutput = await prettier.format(generated.code, { - semi: true, - parser: 'babel', - plugins: [parserBabel, prettierPluginEstree], - }); - return {code: codegenOutput, sourceMapUrl}; -} - function utf16ToUTF8(s: string): string { return unescape(encodeURIComponent(s)); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts index 401cbd4bdf..c648c66043 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -39,7 +39,10 @@ export default function BabelPluginReactCompiler( ) { opts = injectReanimatedFlag(opts); } - if (isDev) { + if ( + opts.environment.enableResetCacheOnSourceFileChanges !== false && + isDev + ) { opts = { ...opts, environment: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 72ed9e7c86..fb951d25c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -15,6 +15,7 @@ import { } from '../HIR/Environment'; import {hasOwnProperty} from '../Utils/utils'; import {fromZodError} from 'zod-validation-error'; +import {CompilerPipelineValue} from './Pipeline'; const PanicThresholdOptionsSchema = z.enum([ /* @@ -209,6 +210,7 @@ export type LoggerEvent = export type Logger = { logEvent: (filename: string | null, event: LoggerEvent) => void; + debugLogIRs?: (value: CompilerPipelineValue) => void; }; export const defaultOptions: PluginOptions = { 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 8a97eea217..6995aec7f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -112,7 +112,7 @@ export type CompilerPipelineValue = | {kind: 'reactive'; name: string; value: ReactiveFunction} | {kind: 'debug'; name: string; value: string}; -export function* run( +function run( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, @@ -122,7 +122,7 @@ export function* run( logger: Logger | null, filename: string | null, code: string | null, -): Generator { +): CodegenFunction { const contextIdentifiers = findContextIdentifiers(func); const env = new Environment( func.scope, @@ -134,12 +134,17 @@ export function* run( code, useMemoCacheIdentifier, ); - yield log({ + env.logger?.debugLogIRs?.({ kind: 'debug', name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - const ast = yield* runWithEnvironment(func, env); + printLog({ + kind: 'debug', + name: 'EnvironmentConfig', + value: prettyFormat(env.config), + }); + const ast = runWithEnvironment(func, env); return ast; } @@ -147,17 +152,22 @@ export function* run( * Note: this is split from run() to make `config` out of scope, so that all * access to feature flags has to be through the Environment for consistency. */ -function* runWithEnvironment( +function runWithEnvironment( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, env: Environment, -): Generator { +): CodegenFunction { + const log = (value: CompilerPipelineValue): CompilerPipelineValue => { + printLog(value); + env.logger?.debugLogIRs?.(value); + return value; + }; const hir = lower(func, env).unwrap(); - yield log({kind: 'hir', name: 'HIR', value: hir}); + log({kind: 'hir', name: 'HIR', value: hir}); pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); validateContextVariableLValues(hir); validateUseMemo(hir); @@ -168,35 +178,35 @@ function* runWithEnvironment( !env.config.enableChangeDetectionForDebugging ) { dropManualMemoization(hir); - yield log({kind: 'hir', name: 'DropManualMemoization', value: hir}); + log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } inlineImmediatelyInvokedFunctionExpressions(hir); - yield log({ + log({ kind: 'hir', name: 'InlineImmediatelyInvokedFunctionExpressions', value: hir, }); mergeConsecutiveBlocks(hir); - yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); + log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); assertConsistentIdentifiers(hir); assertTerminalSuccessorsExist(hir); enterSSA(hir); - yield log({kind: 'hir', name: 'SSA', value: hir}); + log({kind: 'hir', name: 'SSA', value: hir}); eliminateRedundantPhi(hir); - yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); + log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); assertConsistentIdentifiers(hir); constantPropagation(hir); - yield log({kind: 'hir', name: 'ConstantPropagation', value: hir}); + log({kind: 'hir', name: 'ConstantPropagation', value: hir}); inferTypes(hir); - yield log({kind: 'hir', name: 'InferTypes', value: hir}); + log({kind: 'hir', name: 'InferTypes', value: hir}); if (env.config.validateHooksUsage) { validateHooksUsage(hir); @@ -211,30 +221,30 @@ function* runWithEnvironment( } optimizePropsMethodCalls(hir); - yield log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir}); + log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir}); analyseFunctions(hir); - yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); + log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); inferReferenceEffects(hir); - yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); + log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); validateLocalsNotReassignedAfterRender(hir); // Note: Has to come after infer reference effects because "dead" code may still affect inference deadCodeElimination(hir); - yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); + log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); if (env.config.enableInstructionReordering) { instructionReordering(hir); - yield log({kind: 'hir', name: 'InstructionReordering', value: hir}); + log({kind: 'hir', name: 'InstructionReordering', value: hir}); } pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); inferMutableRanges(hir); - yield log({kind: 'hir', name: 'InferMutableRanges', value: hir}); + log({kind: 'hir', name: 'InferMutableRanges', value: hir}); if (env.config.assertValidMutableRanges) { assertValidMutableRanges(hir); @@ -257,27 +267,27 @@ function* runWithEnvironment( } inferReactivePlaces(hir); - yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); + log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); rewriteInstructionKindsBasedOnReassignment(hir); - yield log({ + log({ kind: 'hir', name: 'RewriteInstructionKindsBasedOnReassignment', value: hir, }); propagatePhiTypes(hir); - yield log({ + log({ kind: 'hir', name: 'PropagatePhiTypes', value: hir, }); inferReactiveScopeVariables(hir); - yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); + log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir); - yield log({ + log({ kind: 'hir', name: 'MemoizeFbtAndMacroOperandsInSameScope', value: hir, @@ -289,39 +299,39 @@ function* runWithEnvironment( if (env.config.enableFunctionOutlining) { outlineFunctions(hir, fbtOperands); - yield log({kind: 'hir', name: 'OutlineFunctions', value: hir}); + log({kind: 'hir', name: 'OutlineFunctions', value: hir}); } alignMethodCallScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignMethodCallScopes', value: hir, }); alignObjectMethodScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignObjectMethodScopes', value: hir, }); pruneUnusedLabelsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PruneUnusedLabelsHIR', value: hir, }); alignReactiveScopesToBlockScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'AlignReactiveScopesToBlockScopesHIR', value: hir, }); mergeOverlappingReactiveScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'MergeOverlappingReactiveScopesHIR', value: hir, @@ -329,7 +339,7 @@ function* runWithEnvironment( assertValidBlockNesting(hir); buildReactiveScopeTerminalsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'BuildReactiveScopeTerminalsHIR', value: hir, @@ -338,14 +348,14 @@ function* runWithEnvironment( assertValidBlockNesting(hir); flattenReactiveLoopsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenReactiveLoopsHIR', value: hir, }); flattenScopesWithHooksOrUseHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenScopesWithHooksOrUseHIR', value: hir, @@ -353,7 +363,7 @@ function* runWithEnvironment( assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); propagateScopeDependenciesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PropagateScopeDependenciesHIR', value: hir, @@ -365,7 +375,7 @@ function* runWithEnvironment( if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); - yield log({ + log({ kind: 'hir', name: 'inlineJsxTransform', value: hir, @@ -373,7 +383,7 @@ function* runWithEnvironment( } const reactiveFunction = buildReactiveFunction(hir); - yield log({ + log({ kind: 'reactive', name: 'BuildReactiveFunction', value: reactiveFunction, @@ -382,7 +392,7 @@ function* runWithEnvironment( assertWellFormedBreakTargets(reactiveFunction); pruneUnusedLabels(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLabels', value: reactiveFunction, @@ -390,35 +400,35 @@ function* runWithEnvironment( assertScopeInstructionsWithinScopes(reactiveFunction); pruneNonEscapingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonEscapingScopes', value: reactiveFunction, }); pruneNonReactiveDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonReactiveDependencies', value: reactiveFunction, }); pruneUnusedScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedScopes', value: reactiveFunction, }); mergeReactiveScopesThatInvalidateTogether(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'MergeReactiveScopesThatInvalidateTogether', value: reactiveFunction, }); pruneAlwaysInvalidatingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneAlwaysInvalidatingScopes', value: reactiveFunction, @@ -426,7 +436,7 @@ function* runWithEnvironment( if (env.config.enableChangeDetectionForDebugging != null) { pruneInitializationDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneInitializationDependencies', value: reactiveFunction, @@ -434,49 +444,49 @@ function* runWithEnvironment( } propagateEarlyReturns(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PropagateEarlyReturns', value: reactiveFunction, }); pruneUnusedLValues(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLValues', value: reactiveFunction, }); promoteUsedTemporaries(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PromoteUsedTemporaries', value: reactiveFunction, }); extractScopeDeclarationsFromDestructuring(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'ExtractScopeDeclarationsFromDestructuring', value: reactiveFunction, }); stabilizeBlockIds(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'StabilizeBlockIds', value: reactiveFunction, }); const uniqueIdentifiers = renameVariables(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'RenameVariables', value: reactiveFunction, }); pruneHoistedContexts(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneHoistedContexts', value: reactiveFunction, @@ -497,9 +507,9 @@ function* runWithEnvironment( uniqueIdentifiers, fbtOperands, }).unwrap(); - yield log({kind: 'ast', name: 'Codegen', value: ast}); + log({kind: 'ast', name: 'Codegen', value: ast}); for (const outlined of ast.outlined) { - yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); + log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); } /** @@ -525,7 +535,7 @@ export function compileFn( filename: string | null, code: string | null, ): CodegenFunction { - let generator = run( + return run( func, config, fnType, @@ -534,15 +544,9 @@ export function compileFn( filename, code, ); - while (true) { - const next = generator.next(); - if (next.done) { - return next.value; - } - } } -export function log(value: CompilerPipelineValue): CompilerPipelineValue { +function printLog(value: CompilerPipelineValue): CompilerPipelineValue { switch (value.kind) { case 'ast': { logCodegenFunction(value.name, value.value); @@ -566,14 +570,3 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue { } return value; } - -export function* runPlayground( - func: NodePath< - t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression - >, - config: EnvironmentConfig, - fnType: ReactFunctionType, -): Generator { - const ast = yield* run(func, config, fnType, '_c', null, null, null); - return ast; -} 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 fa581d8ed8..48589b8be9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -168,11 +168,19 @@ const EnvironmentConfigSchema = z.object({ customMacros: z.nullable(z.array(MacroSchema)).default(null), /** - * Enable a check that resets the memoization cache when the source code of the file changes. - * This is intended to support hot module reloading (HMR), where the same runtime component - * instance will be reused across different versions of the component source. + * Enable a check that resets the memoization cache when the source code of + * the file changes. This is intended to support hot module reloading (HMR), + * where the same runtime component instance will be reused across different + * versions of the component source. + * + * When set to + * - true: code for HMR support is always generated, regardless of NODE_ENV + * or `globalThis.__DEV__` + * - false: code for HMR support is not generated + * - null: (default) code for HMR support is conditionally generated dependent + * on `NODE_ENV` and `globalThis.__DEV__` at the time of compilation. */ - enableResetCacheOnSourceFileChanges: z.boolean().default(false), + enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null), /** * Enable using information from existing useMemo/useCallback to understand when a value is done @@ -708,7 +716,10 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { continue; } - if (typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean') { + if ( + key !== 'enableResetCacheOnSourceFileChanges' && + typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean' + ) { // skip parsing non-boolean properties continue; } @@ -718,9 +729,15 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { maybeConfig[key] = false; } } - const config = EnvironmentConfigSchema.safeParse(maybeConfig); if (config.success) { + /** + * Unless explicitly enabled, do not insert HMR handling code + * in test fixtures or playground to reduce visual noise. + */ + if (config.data.enableResetCacheOnSourceFileChanges == null) { + config.data.enableResetCacheOnSourceFileChanges = false; + } return config.data; } CompilerError.invariant(false, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md new file mode 100644 index 0000000000..69c1b9bbbb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -0,0 +1,29 @@ + +## Input + +```javascript +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} + +``` + +## Code + +```javascript +// @compilationMode(all) +"use no memo"; + +function TestComponent({ x }) { + "use memo"; + return ; +} + +``` + +### 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/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js new file mode 100644 index 0000000000..9b314e1f99 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -0,0 +1,7 @@ +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts index d634bd235f..dc4d5d25a4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts @@ -25,6 +25,7 @@ describe('parseConfigPragmaForTests()', () => { enableUseTypeAnnotations: true, validateNoSetStateInPassiveEffects: true, validateNoSetStateInRender: false, + enableResetCacheOnSourceFileChanges: false, }); }); }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 150df26e45..188c244d9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -17,8 +17,6 @@ export { compileFn as compile, compileProgram, parsePluginOptions, - run, - runPlayground, OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, findDirectiveEnablingMemoization, From c9592dd35772c0dcecc631ae95121941ca9d5c09 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 13:46:48 -0500 Subject: [PATCH 126/422] [compiler][be] Playground now compiles entire program Compiler playground now runs the entire program through `babel-plugin-react-compiler` instead of a custom pipeline which previously duplicated function inference logic from `Program.ts`. In addition, the playground output reflects the tranformed file (instead of a "virtual file" of manually concatenated functions). This helps with the following: - Reduce potential discrepencies between playground and babel plugin behavior. See attached fixture output for an example where we previously diverged. - Let playground users see compiler-inserted imports (e.g. `_c` or `useFire`) This also helps us repurpose playground into a more general tool for compiler-users instead of just for compiler engineers. - imports and other functions are preserved. We differentiate between imports and globals in many cases (e.g. `inferEffectDeps`), so it may be misleading to omit imports in printed output - playground now shows other program-changing behavior like position of outlined functions and hoisted declarations - emitted compiled functions do not need synthetic names --- .../page.spec.ts/01-user-output.txt | 3 +- .../page.spec.ts/02-default-output.txt | 3 +- .../module-scope-use-memo-output.txt | 4 +- .../module-scope-use-no-memo-output.txt | 3 +- .../page.spec.ts/parse-flow-output.txt | 14 + .../page.spec.ts/parse-typescript-output.txt | 20 ++ ...cope-does-not-beat-module-scope-output.txt | 5 + .../page.spec.ts/use-memo-output.txt | 5 +- .../page.spec.ts/use-no-memo-output.txt | 8 +- .../playground/__tests__/e2e/page.spec.ts | 41 ++- .../components/Editor/EditorImpl.tsx | 260 ++++-------------- .../playground/components/Editor/Output.tsx | 63 ++--- .../src/Babel/BabelPlugin.ts | 5 +- .../src/Entrypoint/Options.ts | 2 + .../src/Entrypoint/Pipeline.ts | 141 +++++----- .../src/HIR/Environment.ts | 29 +- ...ule-scope-usememo-function-scope.expect.md | 29 ++ ...emo-module-scope-usememo-function-scope.js | 7 + .../src/__tests__/parseConfigPragma-test.ts | 1 + .../babel-plugin-react-compiler/src/index.ts | 2 - 20 files changed, 302 insertions(+), 343 deletions(-) create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-flow-output.txt create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-typescript-output.txt create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt index ba680bbb57..1600f35107 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt @@ -1,4 +1,5 @@ -function TestComponent(t0) { +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt index 2cbd09bba6..1d59a120f9 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt @@ -1,4 +1,5 @@ -function MyApp() { +import { c as _c } from "react/compiler-runtime"; +export default function MyApp() { const $ = _c(1); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt index ba680bbb57..638a2bcd22 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt @@ -1,4 +1,6 @@ -function TestComponent(t0) { +"use memo"; +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { const $ = _c(2); const { x } = t0; let t1; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt index 2c69ddc1d6..ebd2d2b046 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt @@ -1,3 +1,4 @@ -function TestComponent({ x }) { +"use no memo"; +export default function TestComponent({ x }) { return ; } diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-flow-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-flow-output.txt new file mode 100644 index 0000000000..c0907856ba --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-flow-output.txt @@ -0,0 +1,14 @@ +import { c as _c } from "react/compiler-runtime"; +function useFoo(propVal) { +  const $ = _c(2); +  const t0 = (propVal.baz: number); +  let t1; +  if ($[0] !== t0) { +    t1 = 
{t0}
; +    $[0] = t0; +    $[1] = t1; +  } else { +    t1 = $[1]; +  } +  return t1; +} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-typescript-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-typescript-output.txt new file mode 100644 index 0000000000..c936c5ae2e --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/parse-typescript-output.txt @@ -0,0 +1,20 @@ +import { c as _c } from "react/compiler-runtime"; +function Foo() { +  const $ = _c(2); +  let t0; +  if ($[0] === Symbol.for("react.memo_cache_sentinel")) { +    t0 = foo(); +    $[0] = t0; +  } else { +    t0 = $[0]; +  } +  const x = t0 as number; +  let t1; +  if ($[1] === Symbol.for("react.memo_cache_sentinel")) { +    t1 = 
{x}
; +    $[1] = t1; +  } else { +    t1 = $[1]; +  } +  return t1; +} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt new file mode 100644 index 0000000000..325e6972e1 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/todo-function-scope-does-not-beat-module-scope-output.txt @@ -0,0 +1,5 @@ +"use no memo"; +function TestComponent({ x }) { + "use memo"; + return ; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt index 804bacab97..de6dd52680 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt @@ -1,3 +1,4 @@ +import { c as _c } from "react/compiler-runtime"; function TestComponent(t0) { "use memo"; const $ = _c(2); @@ -12,7 +13,7 @@ function TestComponent(t0) { } return t1; } -function anonymous_1(t0) { +const TestComponent2 = (t0) => { "use memo"; const $ = _c(2); const { x } = t0; @@ -25,4 +26,4 @@ function anonymous_1(t0) { t1 = $[1]; } return t1; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt index 5fb66309fc..02c1367622 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt @@ -1,8 +1,8 @@ -function anonymous_1() { +const TestComponent = function () { "use no memo"; return ; -} -function anonymous_3({ x }) { +}; +const TestComponent2 = ({ x }) => { "use no memo"; return ; -} +}; diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 846e6227bd..05fe96d4b9 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -9,11 +9,11 @@ import {expect, test} from '@playwright/test'; import {encodeStore, type Store} from '../../lib/stores'; import {format} from 'prettier'; -function print(data: Array): Promise { +function formatPrint(data: Array): Promise { return format(data.join(''), {parser: 'babel'}); } -const DIRECTIVE_TEST_CASES = [ +const TEST_CASE_INPUTS = [ { name: 'module-scope-use-memo', input: ` @@ -55,7 +55,7 @@ const TestComponent2 = ({ x }) => { };`, }, { - name: 'function-scope-beats-module-scope', + name: 'todo-function-scope-does-not-beat-module-scope', input: ` 'use no memo'; function TestComponent({ x }) { @@ -63,6 +63,26 @@ function TestComponent({ x }) { return ; }`, }, + { + name: 'parse-typescript', + input: ` +function Foo() { + const x = foo() as number; + return
{x}
; +} +`, + noFormat: true, + }, + { + name: 'parse-flow', + input: ` +// @flow +function useFoo(propVal: {+baz: number}) { + return
{(propVal.baz as number)}
; +} + `, + noFormat: true, + }, ]; test('editor should open successfully', async ({page}) => { @@ -90,7 +110,7 @@ test('editor should compile from hash successfully', async ({page}) => { }); const text = (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; - const output = await print(text); + const output = await formatPrint(text); expect(output).not.toEqual(''); expect(output).toMatchSnapshot('01-user-output.txt'); @@ -115,14 +135,14 @@ test('reset button works', async ({page}) => { }); const text = (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; - const output = await print(text); + const output = await formatPrint(text); expect(output).not.toEqual(''); expect(output).toMatchSnapshot('02-default-output.txt'); }); -DIRECTIVE_TEST_CASES.forEach((t, idx) => - test(`directives work: ${t.name}`, async ({page}) => { +TEST_CASE_INPUTS.forEach((t, idx) => + test(`playground compiles: ${t.name}`, async ({page}) => { const store: Store = { source: t.input, }; @@ -135,7 +155,12 @@ DIRECTIVE_TEST_CASES.forEach((t, idx) => const text = (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; - const output = await print(text); + let output: string; + if (t.noFormat) { + output = text.join(''); + } else { + output = await formatPrint(text); + } expect(output).not.toEqual(''); expect(output).toMatchSnapshot(`${t.name}-output.txt`); diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 82a40272bd..785b9fd075 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,23 +5,22 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse} from '@babel/parser'; +import {parse as babelParse, ParseResult} from '@babel/parser'; import * as HermesParser from 'hermes-parser'; -import traverse, {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import { +import BabelPluginReactCompiler, { CompilerError, CompilerErrorDetail, Effect, ErrorSeverity, parseConfigPragmaForTests, ValueKind, - runPlayground, type Hook, - findDirectiveDisablingMemoization, - findDirectiveEnablingMemoization, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, } from 'babel-plugin-react-compiler/src'; -import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment'; import clsx from 'clsx'; import invariant from 'invariant'; import {useSnackbar} from 'notistack'; @@ -39,32 +38,18 @@ import {useStore, useStoreDispatch} from '../StoreContext'; import Input from './Input'; import { CompilerOutput, + CompilerTransformOutput, default as Output, PrintedCompilerPipelineValue, } from './Output'; import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; +import {transformFromAstSync} from '@babel/core'; -type FunctionLike = - | NodePath - | NodePath - | NodePath; -enum MemoizeDirectiveState { - Enabled = 'Enabled', - Disabled = 'Disabled', - Undefined = 'Undefined', -} - -const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Undefined, -]); - -const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Disabled, -]); -function parseInput(input: string, language: 'flow' | 'typescript'): any { +function parseInput( + input: string, + language: 'flow' | 'typescript', +): ParseResult { // Extract the first line to quickly check for custom test directives if (language === 'flow') { return HermesParser.parse(input, { @@ -77,95 +62,45 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { return babelParse(input, { plugins: ['typescript', 'jsx'], sourceType: 'module', - }); + }) as ParseResult; } } -function parseFunctions( +function invokeCompiler( source: string, language: 'flow' | 'typescript', -): Array<{ - compilationEnabled: boolean; - fn: FunctionLike; -}> { - const items: Array<{ - compilationEnabled: boolean; - fn: FunctionLike; - }> = []; - try { - const ast = parseInput(source, language); - traverse(ast, { - FunctionDeclaration(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - ArrowFunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - FunctionExpression(nodePath) { - items.push({ - compilationEnabled: shouldCompile(nodePath), - fn: nodePath, - }); - nodePath.skip(); - }, - }); - } catch (e) { - console.error(e); - CompilerError.throwInvalidJS({ - reason: String(e), - description: null, - loc: null, - suggestions: null, - }); + environment: EnvironmentConfig, + logIR: (pipelineValue: CompilerPipelineValue) => void, +): CompilerTransformOutput { + const opts: PluginOptions = parsePluginOptions({ + logger: { + debugLogIRs: logIR, + logEvent: () => {}, + }, + environment, + compilationMode: 'all', + panicThreshold: 'all_errors', + }); + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [[BabelPluginReactCompiler, opts]], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error('Expected successful compilation'); } - - return items; -} - -function shouldCompile(fn: FunctionLike): boolean { - const {body} = fn.node; - if (t.isBlockStatement(body)) { - const selfCheck = checkExplicitMemoizeDirectives(body.directives); - if (selfCheck === MemoizeDirectiveState.Enabled) return true; - if (selfCheck === MemoizeDirectiveState.Disabled) return false; - - const parentWithDirective = fn.findParent(parentPath => { - if (parentPath.isBlockStatement() || parentPath.isProgram()) { - const directiveCheck = checkExplicitMemoizeDirectives( - parentPath.node.directives, - ); - return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck); - } - return false; - }); - - if (!parentWithDirective) return true; - const parentDirectiveCheck = checkExplicitMemoizeDirectives( - (parentWithDirective.node as t.Program | t.BlockStatement).directives, - ); - return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); - } - return false; -} - -function checkExplicitMemoizeDirectives( - directives: Array, -): MemoizeDirectiveState { - if (findDirectiveEnablingMemoization(directives).length) { - return MemoizeDirectiveState.Enabled; - } - if (findDirectiveDisablingMemoization(directives).length) { - return MemoizeDirectiveState.Disabled; - } - return MemoizeDirectiveState.Undefined; + return { + code: result.code, + sourceMaps: result.map, + language, + }; } const COMMON_HOOKS: Array<[string, Hook]> = [ @@ -216,37 +151,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ ], ]; -function isHookName(s: string): boolean { - return /^use[A-Z0-9]/.test(s); -} - -function getReactFunctionType(id: t.Identifier | null): ReactFunctionType { - if (id != null) { - if (isHookName(id.name)) { - return 'Hook'; - } - - const isPascalCaseNameSpace = /^[A-Z].*/; - if (isPascalCaseNameSpace.test(id.name)) { - return 'Component'; - } - } - return 'Other'; -} - -function getFunctionIdentifier( - fn: - | NodePath - | NodePath - | NodePath, -): t.Identifier | null { - if (fn.isArrowFunctionExpression()) { - return null; - } - const id = fn.get('id'); - return Array.isArray(id) === false && id.isIdentifier() ? id.node : null; -} - function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map>(); const error = new CompilerError(); @@ -264,71 +168,25 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { } else { language = 'typescript'; } - let count = 0; - const withIdentifier = (id: t.Identifier | null): t.Identifier => { - if (id != null && id.name != null) { - return id; - } else { - return t.identifier(`anonymous_${count++}`); - } - }; + let transformOutput; try { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); const config = parseConfigPragmaForTests(pragma); - const parsedFunctions = parseFunctions(source, language); - for (const func of parsedFunctions) { - const id = withIdentifier(getFunctionIdentifier(func.fn)); - const fnName = id.name; - if (!func.compilationEnabled) { - upsert({ - kind: 'ast', - fnName, - name: 'CodeGen', - value: { - type: 'FunctionDeclaration', - id: - func.fn.isArrowFunctionExpression() || - func.fn.isFunctionExpression() - ? withIdentifier(null) - : func.fn.node.id, - async: func.fn.node.async, - generator: !!func.fn.node.generator, - body: func.fn.node.body as t.BlockStatement, - params: func.fn.node.params, - }, - }); - continue; - } - for (const result of runPlayground( - func.fn, - { - ...config, - customHooks: new Map([...COMMON_HOOKS]), - }, - getReactFunctionType(id), - )) { + + transformOutput = invokeCompiler( + source, + language, + {...config, customHooks: new Map([...COMMON_HOOKS])}, + result => { switch (result.kind) { case 'ast': { - upsert({ - kind: 'ast', - fnName, - name: result.name, - value: { - type: 'FunctionDeclaration', - id: withIdentifier(result.value.id), - async: result.value.async, - generator: result.value.generator, - body: result.value.body, - params: result.value.params, - }, - }); break; } case 'hir': { upsert({ kind: 'hir', - fnName, + fnName: result.value.id, name: result.name, value: printFunctionWithOutlined(result.value), }); @@ -337,7 +195,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'reactive': { upsert({ kind: 'reactive', - fnName, + fnName: result.value.id, name: result.name, value: printReactiveFunctionWithOutlined(result.value), }); @@ -346,7 +204,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { case 'debug': { upsert({ kind: 'debug', - fnName, + fnName: null, name: result.name, value: result.value, }); @@ -357,8 +215,8 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { throw new Error(`Unhandled result ${result}`); } } - } - } + }, + ); } catch (err) { /** * error might be an invariant violation or other runtime error @@ -385,7 +243,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { if (error.hasErrors()) { return [{kind: 'err', results, error: error}, language]; } - return [{kind: 'ok', results}, language]; + return [{kind: 'ok', results, transformOutput}, language]; } export default function Editor(): JSX.Element { @@ -405,7 +263,7 @@ export default function Editor(): JSX.Element { } catch (e) { invariant(e instanceof Error, 'Only Error may be caught.'); enqueueSnackbar(e.message, { - variant: 'message', + variant: 'warning', ...createMessage( 'Bad URL - fell back to the default Playground.', MessageLevel.Info, diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 97a63400d2..d4127c63cf 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -5,8 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import generate from '@babel/generator'; -import * as t from '@babel/types'; import { CodeIcon, DocumentAddIcon, @@ -21,17 +19,12 @@ import {memo, ReactNode, useEffect, useState} from 'react'; import {type Store} from '../../lib/stores'; import TabbedWindow from '../TabbedWindow'; import {monacoOptions} from './monacoOptions'; +import {BabelFileResult} from '@babel/core'; const MemoizedOutput = memo(Output); export default MemoizedOutput; export type PrintedCompilerPipelineValue = - | { - kind: 'ast'; - name: string; - fnName: string | null; - value: t.FunctionDeclaration; - } | { kind: 'hir'; name: string; @@ -41,8 +34,17 @@ export type PrintedCompilerPipelineValue = | {kind: 'reactive'; name: string; fnName: string | null; value: string} | {kind: 'debug'; name: string; fnName: string | null; value: string}; +export type CompilerTransformOutput = { + code: string; + sourceMaps: BabelFileResult['map']; + language: 'flow' | 'typescript'; +}; export type CompilerOutput = - | {kind: 'ok'; results: Map>} + | { + kind: 'ok'; + transformOutput: CompilerTransformOutput; + results: Map>; + } | { kind: 'err'; results: Map>; @@ -61,7 +63,6 @@ async function tabify( const tabs = new Map(); const reorderedTabs = new Map(); const concattedResults = new Map(); - let topLevelFnDecls: Array = []; // Concat all top level function declaration results into a single tab for each pass for (const [passName, results] of compilerOutput.results) { for (const result of results) { @@ -87,9 +88,6 @@ async function tabify( } break; } - case 'ast': - topLevelFnDecls.push(result.value); - break; case 'debug': { concattedResults.set(passName, result.value); break; @@ -114,13 +112,17 @@ async function tabify( lastPassOutput = text; } // Ensure that JS and the JS source map come first - if (topLevelFnDecls.length > 0) { - /** - * Make a synthetic Program so we can have a single AST with all the top level - * FunctionDeclarations - */ - const ast = t.program(topLevelFnDecls); - const {code, sourceMapUrl} = await codegen(ast, source); + if (compilerOutput.kind === 'ok') { + const {transformOutput} = compilerOutput; + const sourceMapUrl = getSourceMapUrl( + transformOutput.code, + JSON.stringify(transformOutput.sourceMaps), + ); + const code = await prettier.format(transformOutput.code, { + semi: true, + parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts', + plugins: [parserBabel, prettierPluginEstree], + }); reorderedTabs.set( 'JS', { - const generated = generate( - ast, - {sourceMaps: true, sourceFileName: 'input.js'}, - source, - ); - const sourceMapUrl = getSourceMapUrl( - generated.code, - JSON.stringify(generated.map), - ); - const codegenOutput = await prettier.format(generated.code, { - semi: true, - parser: 'babel', - plugins: [parserBabel, prettierPluginEstree], - }); - return {code: codegenOutput, sourceMapUrl}; -} - function utf16ToUTF8(s: string): string { return unescape(encodeURIComponent(s)); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts index 401cbd4bdf..c648c66043 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -39,7 +39,10 @@ export default function BabelPluginReactCompiler( ) { opts = injectReanimatedFlag(opts); } - if (isDev) { + if ( + opts.environment.enableResetCacheOnSourceFileChanges !== false && + isDev + ) { opts = { ...opts, environment: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 72ed9e7c86..fb951d25c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -15,6 +15,7 @@ import { } from '../HIR/Environment'; import {hasOwnProperty} from '../Utils/utils'; import {fromZodError} from 'zod-validation-error'; +import {CompilerPipelineValue} from './Pipeline'; const PanicThresholdOptionsSchema = z.enum([ /* @@ -209,6 +210,7 @@ export type LoggerEvent = export type Logger = { logEvent: (filename: string | null, event: LoggerEvent) => void; + debugLogIRs?: (value: CompilerPipelineValue) => void; }; export const defaultOptions: PluginOptions = { 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 8a97eea217..6995aec7f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -112,7 +112,7 @@ export type CompilerPipelineValue = | {kind: 'reactive'; name: string; value: ReactiveFunction} | {kind: 'debug'; name: string; value: string}; -export function* run( +function run( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, @@ -122,7 +122,7 @@ export function* run( logger: Logger | null, filename: string | null, code: string | null, -): Generator { +): CodegenFunction { const contextIdentifiers = findContextIdentifiers(func); const env = new Environment( func.scope, @@ -134,12 +134,17 @@ export function* run( code, useMemoCacheIdentifier, ); - yield log({ + env.logger?.debugLogIRs?.({ kind: 'debug', name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - const ast = yield* runWithEnvironment(func, env); + printLog({ + kind: 'debug', + name: 'EnvironmentConfig', + value: prettyFormat(env.config), + }); + const ast = runWithEnvironment(func, env); return ast; } @@ -147,17 +152,22 @@ export function* run( * Note: this is split from run() to make `config` out of scope, so that all * access to feature flags has to be through the Environment for consistency. */ -function* runWithEnvironment( +function runWithEnvironment( func: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, env: Environment, -): Generator { +): CodegenFunction { + const log = (value: CompilerPipelineValue): CompilerPipelineValue => { + printLog(value); + env.logger?.debugLogIRs?.(value); + return value; + }; const hir = lower(func, env).unwrap(); - yield log({kind: 'hir', name: 'HIR', value: hir}); + log({kind: 'hir', name: 'HIR', value: hir}); pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); validateContextVariableLValues(hir); validateUseMemo(hir); @@ -168,35 +178,35 @@ function* runWithEnvironment( !env.config.enableChangeDetectionForDebugging ) { dropManualMemoization(hir); - yield log({kind: 'hir', name: 'DropManualMemoization', value: hir}); + log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } inlineImmediatelyInvokedFunctionExpressions(hir); - yield log({ + log({ kind: 'hir', name: 'InlineImmediatelyInvokedFunctionExpressions', value: hir, }); mergeConsecutiveBlocks(hir); - yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); + log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); assertConsistentIdentifiers(hir); assertTerminalSuccessorsExist(hir); enterSSA(hir); - yield log({kind: 'hir', name: 'SSA', value: hir}); + log({kind: 'hir', name: 'SSA', value: hir}); eliminateRedundantPhi(hir); - yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); + log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); assertConsistentIdentifiers(hir); constantPropagation(hir); - yield log({kind: 'hir', name: 'ConstantPropagation', value: hir}); + log({kind: 'hir', name: 'ConstantPropagation', value: hir}); inferTypes(hir); - yield log({kind: 'hir', name: 'InferTypes', value: hir}); + log({kind: 'hir', name: 'InferTypes', value: hir}); if (env.config.validateHooksUsage) { validateHooksUsage(hir); @@ -211,30 +221,30 @@ function* runWithEnvironment( } optimizePropsMethodCalls(hir); - yield log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir}); + log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir}); analyseFunctions(hir); - yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); + log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); inferReferenceEffects(hir); - yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); + log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); validateLocalsNotReassignedAfterRender(hir); // Note: Has to come after infer reference effects because "dead" code may still affect inference deadCodeElimination(hir); - yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); + log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); if (env.config.enableInstructionReordering) { instructionReordering(hir); - yield log({kind: 'hir', name: 'InstructionReordering', value: hir}); + log({kind: 'hir', name: 'InstructionReordering', value: hir}); } pruneMaybeThrows(hir); - yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); + log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); inferMutableRanges(hir); - yield log({kind: 'hir', name: 'InferMutableRanges', value: hir}); + log({kind: 'hir', name: 'InferMutableRanges', value: hir}); if (env.config.assertValidMutableRanges) { assertValidMutableRanges(hir); @@ -257,27 +267,27 @@ function* runWithEnvironment( } inferReactivePlaces(hir); - yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); + log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); rewriteInstructionKindsBasedOnReassignment(hir); - yield log({ + log({ kind: 'hir', name: 'RewriteInstructionKindsBasedOnReassignment', value: hir, }); propagatePhiTypes(hir); - yield log({ + log({ kind: 'hir', name: 'PropagatePhiTypes', value: hir, }); inferReactiveScopeVariables(hir); - yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); + log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir}); const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir); - yield log({ + log({ kind: 'hir', name: 'MemoizeFbtAndMacroOperandsInSameScope', value: hir, @@ -289,39 +299,39 @@ function* runWithEnvironment( if (env.config.enableFunctionOutlining) { outlineFunctions(hir, fbtOperands); - yield log({kind: 'hir', name: 'OutlineFunctions', value: hir}); + log({kind: 'hir', name: 'OutlineFunctions', value: hir}); } alignMethodCallScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignMethodCallScopes', value: hir, }); alignObjectMethodScopes(hir); - yield log({ + log({ kind: 'hir', name: 'AlignObjectMethodScopes', value: hir, }); pruneUnusedLabelsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PruneUnusedLabelsHIR', value: hir, }); alignReactiveScopesToBlockScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'AlignReactiveScopesToBlockScopesHIR', value: hir, }); mergeOverlappingReactiveScopesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'MergeOverlappingReactiveScopesHIR', value: hir, @@ -329,7 +339,7 @@ function* runWithEnvironment( assertValidBlockNesting(hir); buildReactiveScopeTerminalsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'BuildReactiveScopeTerminalsHIR', value: hir, @@ -338,14 +348,14 @@ function* runWithEnvironment( assertValidBlockNesting(hir); flattenReactiveLoopsHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenReactiveLoopsHIR', value: hir, }); flattenScopesWithHooksOrUseHIR(hir); - yield log({ + log({ kind: 'hir', name: 'FlattenScopesWithHooksOrUseHIR', value: hir, @@ -353,7 +363,7 @@ function* runWithEnvironment( assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); propagateScopeDependenciesHIR(hir); - yield log({ + log({ kind: 'hir', name: 'PropagateScopeDependenciesHIR', value: hir, @@ -365,7 +375,7 @@ function* runWithEnvironment( if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); - yield log({ + log({ kind: 'hir', name: 'inlineJsxTransform', value: hir, @@ -373,7 +383,7 @@ function* runWithEnvironment( } const reactiveFunction = buildReactiveFunction(hir); - yield log({ + log({ kind: 'reactive', name: 'BuildReactiveFunction', value: reactiveFunction, @@ -382,7 +392,7 @@ function* runWithEnvironment( assertWellFormedBreakTargets(reactiveFunction); pruneUnusedLabels(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLabels', value: reactiveFunction, @@ -390,35 +400,35 @@ function* runWithEnvironment( assertScopeInstructionsWithinScopes(reactiveFunction); pruneNonEscapingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonEscapingScopes', value: reactiveFunction, }); pruneNonReactiveDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneNonReactiveDependencies', value: reactiveFunction, }); pruneUnusedScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedScopes', value: reactiveFunction, }); mergeReactiveScopesThatInvalidateTogether(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'MergeReactiveScopesThatInvalidateTogether', value: reactiveFunction, }); pruneAlwaysInvalidatingScopes(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneAlwaysInvalidatingScopes', value: reactiveFunction, @@ -426,7 +436,7 @@ function* runWithEnvironment( if (env.config.enableChangeDetectionForDebugging != null) { pruneInitializationDependencies(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneInitializationDependencies', value: reactiveFunction, @@ -434,49 +444,49 @@ function* runWithEnvironment( } propagateEarlyReturns(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PropagateEarlyReturns', value: reactiveFunction, }); pruneUnusedLValues(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneUnusedLValues', value: reactiveFunction, }); promoteUsedTemporaries(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PromoteUsedTemporaries', value: reactiveFunction, }); extractScopeDeclarationsFromDestructuring(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'ExtractScopeDeclarationsFromDestructuring', value: reactiveFunction, }); stabilizeBlockIds(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'StabilizeBlockIds', value: reactiveFunction, }); const uniqueIdentifiers = renameVariables(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'RenameVariables', value: reactiveFunction, }); pruneHoistedContexts(reactiveFunction); - yield log({ + log({ kind: 'reactive', name: 'PruneHoistedContexts', value: reactiveFunction, @@ -497,9 +507,9 @@ function* runWithEnvironment( uniqueIdentifiers, fbtOperands, }).unwrap(); - yield log({kind: 'ast', name: 'Codegen', value: ast}); + log({kind: 'ast', name: 'Codegen', value: ast}); for (const outlined of ast.outlined) { - yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); + log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn}); } /** @@ -525,7 +535,7 @@ export function compileFn( filename: string | null, code: string | null, ): CodegenFunction { - let generator = run( + return run( func, config, fnType, @@ -534,15 +544,9 @@ export function compileFn( filename, code, ); - while (true) { - const next = generator.next(); - if (next.done) { - return next.value; - } - } } -export function log(value: CompilerPipelineValue): CompilerPipelineValue { +function printLog(value: CompilerPipelineValue): CompilerPipelineValue { switch (value.kind) { case 'ast': { logCodegenFunction(value.name, value.value); @@ -566,14 +570,3 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue { } return value; } - -export function* runPlayground( - func: NodePath< - t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression - >, - config: EnvironmentConfig, - fnType: ReactFunctionType, -): Generator { - const ast = yield* run(func, config, fnType, '_c', null, null, null); - return ast; -} 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 fa581d8ed8..48589b8be9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -168,11 +168,19 @@ const EnvironmentConfigSchema = z.object({ customMacros: z.nullable(z.array(MacroSchema)).default(null), /** - * Enable a check that resets the memoization cache when the source code of the file changes. - * This is intended to support hot module reloading (HMR), where the same runtime component - * instance will be reused across different versions of the component source. + * Enable a check that resets the memoization cache when the source code of + * the file changes. This is intended to support hot module reloading (HMR), + * where the same runtime component instance will be reused across different + * versions of the component source. + * + * When set to + * - true: code for HMR support is always generated, regardless of NODE_ENV + * or `globalThis.__DEV__` + * - false: code for HMR support is not generated + * - null: (default) code for HMR support is conditionally generated dependent + * on `NODE_ENV` and `globalThis.__DEV__` at the time of compilation. */ - enableResetCacheOnSourceFileChanges: z.boolean().default(false), + enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null), /** * Enable using information from existing useMemo/useCallback to understand when a value is done @@ -708,7 +716,10 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { continue; } - if (typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean') { + if ( + key !== 'enableResetCacheOnSourceFileChanges' && + typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean' + ) { // skip parsing non-boolean properties continue; } @@ -718,9 +729,15 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { maybeConfig[key] = false; } } - const config = EnvironmentConfigSchema.safeParse(maybeConfig); if (config.success) { + /** + * Unless explicitly enabled, do not insert HMR handling code + * in test fixtures or playground to reduce visual noise. + */ + if (config.data.enableResetCacheOnSourceFileChanges == null) { + config.data.enableResetCacheOnSourceFileChanges = false; + } return config.data; } CompilerError.invariant(false, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md new file mode 100644 index 0000000000..69c1b9bbbb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -0,0 +1,29 @@ + +## Input + +```javascript +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} + +``` + +## Code + +```javascript +// @compilationMode(all) +"use no memo"; + +function TestComponent({ x }) { + "use memo"; + return ; +} + +``` + +### 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/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js new file mode 100644 index 0000000000..9b314e1f99 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -0,0 +1,7 @@ +// @compilationMode(all) +'use no memo'; + +function TestComponent({x}) { + 'use memo'; + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts index d634bd235f..dc4d5d25a4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts @@ -25,6 +25,7 @@ describe('parseConfigPragmaForTests()', () => { enableUseTypeAnnotations: true, validateNoSetStateInPassiveEffects: true, validateNoSetStateInRender: false, + enableResetCacheOnSourceFileChanges: false, }); }); }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 150df26e45..188c244d9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -17,8 +17,6 @@ export { compileFn as compile, compileProgram, parsePluginOptions, - run, - runPlayground, OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, findDirectiveEnablingMemoization, From 1ae728a610ef4c7e8a691d53ca5cdbc48841c545 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 13:48:29 -0500 Subject: [PATCH 127/422] [compiler][be] Logger based debug printing in test runner Avoid mutable logging enabled state and writing to `process.stdout` within our babel transform. --- .../src/Entrypoint/Pipeline.ts | 44 +------ .../src/Inference/AnalyseFunctions.ts | 7 +- .../InferReactiveScopeVariables.ts | 7 +- .../src/Utils/logger.ts | 110 ------------------ compiler/packages/snap/src/compiler.ts | 22 ++-- compiler/packages/snap/src/constants.ts | 12 +- compiler/packages/snap/src/runner-worker.ts | 36 +++++- 7 files changed, 67 insertions(+), 171 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/logger.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 6995aec7f1..c48cba32b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -79,13 +79,6 @@ import { rewriteInstructionKindsBasedOnReassignment, } from '../SSA'; import {inferTypes} from '../TypeInference'; -import { - logCodegenFunction, - logDebug, - logHIRFunction, - logReactiveFunction, -} from '../Utils/logger'; -import {assertExhaustive} from '../Utils/utils'; import { validateContextVariableLValues, validateHooksUsage, @@ -139,13 +132,7 @@ function run( name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - printLog({ - kind: 'debug', - name: 'EnvironmentConfig', - value: prettyFormat(env.config), - }); - const ast = runWithEnvironment(func, env); - return ast; + return runWithEnvironment(func, env); } /* @@ -158,10 +145,8 @@ function runWithEnvironment( >, env: Environment, ): CodegenFunction { - const log = (value: CompilerPipelineValue): CompilerPipelineValue => { - printLog(value); + const log = (value: CompilerPipelineValue): void => { env.logger?.debugLogIRs?.(value); - return value; }; const hir = lower(func, env).unwrap(); log({kind: 'hir', name: 'HIR', value: hir}); @@ -545,28 +530,3 @@ export function compileFn( code, ); } - -function printLog(value: CompilerPipelineValue): CompilerPipelineValue { - switch (value.kind) { - case 'ast': { - logCodegenFunction(value.name, value.value); - break; - } - case 'hir': { - logHIRFunction(value.name, value.value); - break; - } - case 'reactive': { - logReactiveFunction(value.name, value.value); - break; - } - case 'debug': { - logDebug(value.name, value.value); - break; - } - default: { - assertExhaustive(value, 'Unexpected compilation kind'); - } - } - return value; -} 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..75bd8f6811 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -19,7 +19,6 @@ import { import {deadCodeElimination} from '../Optimization'; import {inferReactiveScopeVariables} from '../ReactiveScopes'; import {rewriteInstructionKindsBasedOnReassignment} from '../SSA'; -import {logHIRFunction} from '../Utils/logger'; import {inferMutableContextVariables} from './InferMutableContextVariables'; import {inferMutableRanges} from './InferMutableRanges'; import inferReferenceEffects from './InferReferenceEffects'; @@ -112,7 +111,11 @@ function lower(func: HIRFunction): void { rewriteInstructionKindsBasedOnReassignment(func); inferReactiveScopeVariables(func); inferMutableContextVariables(func); - logHIRFunction('AnalyseFunction (inner)', func); + func.env.logger?.debugLogIRs?.({ + kind: 'hir', + name: 'AnalyseFunction (inner)', + value: func, + }); } function infer( diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts index 098139b150..1108422f07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -25,7 +25,6 @@ import { eachPatternOperand, } from '../HIR/visitors'; import DisjointSet from '../Utils/DisjointSet'; -import {logHIRFunction} from '../Utils/logger'; import {assertExhaustive} from '../Utils/utils'; /* @@ -156,7 +155,11 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void { scope.range.end > maxInstruction + 1 ) { // Make it easier to debug why the error occurred - logHIRFunction('InferReactiveScopeVariables (invalid scope)', fn); + fn.env.logger?.debugLogIRs?.({ + kind: 'hir', + name: 'InferReactiveScopeVariables (invalid scope)', + value: fn, + }); CompilerError.invariant(false, { reason: `Invalid mutable range for scope`, loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts deleted file mode 100644 index fa43a8befe..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts +++ /dev/null @@ -1,110 +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 generate from '@babel/generator'; -import * as t from '@babel/types'; -import chalk from 'chalk'; -import {HIR, HIRFunction, ReactiveFunction} from '../HIR/HIR'; -import {printFunctionWithOutlined, printHIR} from '../HIR/PrintHIR'; -import {CodegenFunction} from '../ReactiveScopes'; -import {printReactiveFunctionWithOutlined} from '../ReactiveScopes/PrintReactiveFunction'; - -let ENABLED: boolean = false; - -let lastLogged: string; - -export function toggleLogging(enabled: boolean): void { - ENABLED = enabled; -} - -export function logDebug(step: string, value: string): void { - if (ENABLED) { - process.stdout.write(`${chalk.green(step)}:\n${value}\n\n`); - } -} - -export function logHIR(step: string, ir: HIR): void { - if (ENABLED) { - const printed = printHIR(ir); - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function logCodegenFunction(step: string, fn: CodegenFunction): void { - if (ENABLED) { - let printed: string | null = null; - try { - const node = t.functionDeclaration( - fn.id, - fn.params, - fn.body, - fn.generator, - fn.async, - ); - const ast = generate(node); - printed = ast.code; - } catch (e) { - let errMsg: string; - if ( - typeof e === 'object' && - e != null && - 'message' in e && - typeof e.message === 'string' - ) { - errMsg = e.message.toString(); - } else { - errMsg = '[empty]'; - } - console.log('Error formatting AST: ' + errMsg); - } - if (printed === null) { - return; - } - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function logHIRFunction(step: string, fn: HIRFunction): void { - if (ENABLED) { - const printed = printFunctionWithOutlined(fn); - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function logReactiveFunction(step: string, fn: ReactiveFunction): void { - if (ENABLED) { - const printed = printReactiveFunctionWithOutlined(fn); - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function log(fn: () => string): void { - if (ENABLED) { - const message = fn(); - process.stdout.write(message.trim() + '\n\n'); - } -} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 1cb8fe48b9..5866445d2e 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -19,6 +19,7 @@ import type { PanicThresholdOptions, PluginOptions, CompilerReactTarget, + CompilerPipelineValue, } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; import type { @@ -45,6 +46,7 @@ export function parseLanguage(source: string): 'flow' | 'typescript' { function makePluginOptions( firstLine: string, parseConfigPragmaFn: typeof ParseConfigPragma, + debugIRLogger: (value: CompilerPipelineValue) => void, EffectEnum: typeof Effect, ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { @@ -182,15 +184,15 @@ function makePluginOptions( .filter(s => s.length > 0); } - let logs: Array<{filename: string | null; event: LoggerEvent}> = []; - let logger: Logger | null = null; - if (firstLine.includes('@logger')) { - logger = { - logEvent(filename: string | null, event: LoggerEvent): void { - logs.push({filename, event}); - }, - }; - } + const logs: Array<{filename: string | null; event: LoggerEvent}> = []; + const logger: Logger = { + logEvent: firstLine.includes('@logger') + ? (filename, event) => { + logs.push({filename, event}); + } + : () => {}, + debugLogIRs: debugIRLogger, + }; const config = parseConfigPragmaFn(firstLine); const options = { @@ -338,6 +340,7 @@ export async function transformFixtureInput( parseConfigPragmaFn: typeof ParseConfigPragma, plugin: BabelCore.PluginObj, includeEvaluator: boolean, + debugIRLogger: (value: CompilerPipelineValue) => void, EffectEnum: typeof Effect, ValueKindEnum: typeof ValueKind, ): Promise<{kind: 'ok'; value: TransformResult} | {kind: 'err'; msg: string}> { @@ -365,6 +368,7 @@ export async function transformFixtureInput( const [options, logs] = makePluginOptions( firstLine, parseConfigPragmaFn, + debugIRLogger, EffectEnum, ValueKindEnum, ); diff --git a/compiler/packages/snap/src/constants.ts b/compiler/packages/snap/src/constants.ts index abee06c55b..ad77441b53 100644 --- a/compiler/packages/snap/src/constants.ts +++ b/compiler/packages/snap/src/constants.ts @@ -18,11 +18,17 @@ export const COMPILER_PATH = path.join( 'BabelPlugin.js', ); export const COMPILER_INDEX_PATH = path.join(process.cwd(), 'dist', 'index'); -export const LOGGER_PATH = path.join( +export const PRINT_HIR_PATH = path.join( process.cwd(), 'dist', - 'Utils', - 'logger.js', + 'HIR', + 'PrintHIR.js', +); +export const PRINT_REACTIVE_IR_PATH = path.join( + process.cwd(), + 'dist', + 'ReactiveScopes', + 'PrintReactiveFunction.js', ); export const PARSE_CONFIG_PRAGMA_PATH = path.join( process.cwd(), diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index f05757d3df..01c525eb90 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -8,16 +8,20 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {PluginObj} from '@babel/core'; import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import type {printFunctionWithOutlined as PrintFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; +import type {printReactiveFunctionWithOutlined as PrintReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; import {TransformResult, transformFixtureInput} from './compiler'; import { COMPILER_PATH, COMPILER_INDEX_PATH, - LOGGER_PATH, PARSE_CONFIG_PRAGMA_PATH, + PRINT_HIR_PATH, + PRINT_REACTIVE_IR_PATH, } from './constants'; import {TestFixture, getBasename, isExpectError} from './fixture-utils'; import {TestResult, writeOutputToString} from './reporter'; import {runSprout} from './sprout'; +import {CompilerPipelineValue} from 'babel-plugin-react-compiler/src'; const originalConsoleError = console.error; @@ -64,20 +68,46 @@ async function compile( const {Effect: EffectEnum, ValueKind: ValueKindEnum} = require( COMPILER_INDEX_PATH, ); - const {toggleLogging} = require(LOGGER_PATH); + const {printFunctionWithOutlined} = require(PRINT_HIR_PATH) as { + printFunctionWithOutlined: typeof PrintFunctionWithOutlined; + }; + const {printReactiveFunctionWithOutlined} = require( + PRINT_REACTIVE_IR_PATH, + ) as { + printReactiveFunctionWithOutlined: typeof PrintReactiveFunctionWithOutlined; + }; + + const debugIRLogger = shouldLog + ? (value: CompilerPipelineValue) => { + switch (value.kind) { + case 'hir': + console.log(printFunctionWithOutlined(value.value)); + break; + case 'reactive': + console.log(printReactiveFunctionWithOutlined(value.value)); + break; + case 'debug': + console.log(value.value); + break; + case 'ast': + // skip printing ast as we already write fixture output JS + break; + } + } + : () => {}; const {parseConfigPragmaForTests} = require(PARSE_CONFIG_PRAGMA_PATH) as { parseConfigPragmaForTests: typeof ParseConfigPragma; }; // only try logging if we filtered out all but one fixture, // since console log order is non-deterministic - toggleLogging(shouldLog); const result = await transformFixtureInput( input, fixturePath, parseConfigPragmaForTests, BabelPluginReactCompiler, includeEvaluator, + debugIRLogger, EffectEnum, ValueKindEnum, ); From 08d6ad72b3f35239f5233a0bb99ab39d728c5d3d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 14:06:37 -0500 Subject: [PATCH 128/422] [compiler][ez] Clean up duplicate code in propagateScopeDeps Clean up duplicate checks for when to skip processing values as dependencies / hoistable temporaries. --- .../src/HIR/PropagateScopeDependenciesHIR.ts | 82 +++++++++++++------ 1 file changed, 58 insertions(+), 24 deletions(-) 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 8aed17f8ee..4a85a4ef5c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -358,6 +358,7 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + #processedInstrsInOptional: ReadonlySet; /** * Tracks the traversal state. See Context.declare for explanation of why this @@ -368,9 +369,11 @@ class Context { constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, + processedInstrsInOptional: ReadonlySet, ) { this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; this.#temporaries = temporaries; + this.#processedInstrsInOptional = processedInstrsInOptional; } enterScope(scope: ReactiveScope): void { @@ -574,22 +577,49 @@ class Context { currentScope.reassignments.add(place.identifier); } } + enterInnerFn(cb: () => T): T { + const wasInInnerFn = this.inInnerFn; + this.inInnerFn = true; + const result = cb(); + this.inInnerFn = wasInInnerFn; + return result; + } + + /** + * Skip dependencies that are subexpressions of other dependencies. e.g. if a + * dependency is tracked in the temporaries sidemap, it can be added at + * site-of-use + */ + isDeferredDependency( + instr: + | {kind: HIRValue.Instruction; value: Instruction} + | {kind: HIRValue.Terminal; value: Terminal}, + ): boolean { + return ( + this.#processedInstrsInOptional.has(instr.value) || + (instr.kind === HIRValue.Instruction && + this.#temporaries.has(instr.value.lvalue.identifier.id)) + ); + } +} +enum HIRValue { + Instruction = 1, + Terminal, } function handleInstruction(instr: Instruction, context: Context): void { const {id, value, lvalue} = instr; - if (value.kind === 'LoadLocal') { - if ( - value.place.identifier.name === null || - lvalue.identifier.name !== null || - context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (context.isUsedOutsideDeclaringScope(lvalue)) { - context.visitProperty(value.object, value.property, false); - } + context.declare(lvalue.identifier, { + id, + scope: context.currentScope, + }); + if ( + context.isDeferredDependency({kind: HIRValue.Instruction, value: instr}) + ) { + return; + } + if (value.kind === 'PropertyLoad') { + context.visitProperty(value.object, value.property, false); } else if (value.kind === 'StoreLocal') { context.visitOperand(value.value); if (value.lvalue.kind === InstructionKind.Reassign) { @@ -632,11 +662,6 @@ function handleInstruction(instr: Instruction, context: Context): void { context.visitOperand(operand); } } - - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); } function collectDependencies( @@ -645,7 +670,11 @@ function collectDependencies( temporaries: ReadonlyMap, processedInstrsInOptional: ReadonlySet, ): Map> { - const context = new Context(usedOutsideDeclaringScope, temporaries); + const context = new Context( + usedOutsideDeclaringScope, + temporaries, + processedInstrsInOptional, + ); for (const param of fn.params) { if (param.kind === 'Identifier') { @@ -694,16 +723,21 @@ function collectDependencies( /** * 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 (!processedInstrsInOptional.has(instr)) { + const innerFn = instr.value.loweredFunc.func; + context.enterInnerFn(() => { + handleFunction(innerFn); + }); + } else { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal)) { + if ( + !context.isDeferredDependency({ + kind: HIRValue.Terminal, + value: block.terminal, + }) + ) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } From 6240edf054204f5bd67a0c76cd327750e4e8440e Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 14:06:37 -0500 Subject: [PATCH 129/422] [compiler] Context variables as dependencies We previously didn't track context variables in the hoistable values sidemap of `propagateScopeDependencies`. This was overly conservative as we *do* track the mutable range of context variables, and it is safe to hoist accesses to context variables after their last direct / aliased maybe-assignment. ```js function Component({value}) { // start of mutable range for `x` let x = DEFAULT; const setX = () => x = value; const aliasedSet = maybeAlias(setX); maybeCall(aliasedSet); // end of mutable range for `x` // here, we should be able to take x (and property reads // off of x) as dependencies return } ``` --- .../src/HIR/HIR.ts | 11 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 65 ++++++--- .../bug-functiondecl-hoisting.expect.md | 18 ++- ...-array-assignment-to-context-var.expect.md | 15 +- ...array-declaration-to-context-var.expect.md | 15 +- ...object-assignment-to-context-var.expect.md | 15 +- ...bject-declaration-to-context-var.expect.md | 15 +- ...mutated-non-reactive-to-reactive.expect.md | 16 +-- ...ures-reassigned-context-property.expect.md | 53 ------- ...ures-reassigned-context-property.expect.md | 101 ++++++++++++++ ...-captures-reassigned-context-property.tsx} | 0 ...o-reordering-depslist-assignment.expect.md | 15 +- ...epro-scope-missing-mutable-range.expect.md | 16 +-- ...l-dependency-on-context-variable.expect.md | 16 +-- .../context-var-granular-dep.expect.md | 130 ++++++++++++++++++ .../context-var-granular-dep.js | 43 ++++++ ...epro-scope-missing-mutable-range.expect.md | 16 +-- .../use-operator-conditional.expect.md | 42 +++--- compiler/packages/snap/src/sprout/index.ts | 10 +- .../snap/src/sprout/shared-runtime.ts | 29 ++-- 20 files changed, 447 insertions(+), 194 deletions(-) delete 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/useCallback-captures-reassigned-context-property.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{error.todo-useCallback-captures-reassigned-context-property.tsx => useCallback-captures-reassigned-context-property.tsx} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js 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 475d8e8bec..a6a9cbcd48 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -840,6 +840,11 @@ export type LoadLocal = { place: Place; loc: SourceLocation; }; +export type LoadContext = { + kind: 'LoadContext'; + place: Place; + loc: SourceLocation; +}; /* * The value of a given instruction. Note that values are not recursive: complex @@ -852,11 +857,7 @@ export type LoadLocal = { export type InstructionValue = | LoadLocal - | { - kind: 'LoadContext'; - place: Place; - loc: SourceLocation; - } + | LoadContext | { kind: 'DeclareLocal'; lvalue: LValue; 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 4a85a4ef5c..08856e9143 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -17,6 +17,11 @@ import { areEqualPaths, IdentifierId, Terminal, + InstructionValue, + LoadContext, + TInstruction, + FunctionExpression, + ObjectMethod, } from './HIR'; import { collectHoistablePropertyLoads, @@ -223,11 +228,25 @@ export function collectTemporariesSidemap( fn, usedOutsideDeclaringScope, temporaries, - false, + null, ); return temporaries; } +function isLoadContextMutable( + instrValue: InstructionValue, + id: InstructionId, +): instrValue is LoadContext { + if (instrValue.kind === 'LoadContext') { + CompilerError.invariant(instrValue.place.identifier.scope != null, { + reason: + '[PropagateScopeDependencies] Expected all context variables to be assigned a scope', + loc: instrValue.loc, + }); + return id >= instrValue.place.identifier.scope.range.end; + } + return false; +} /** * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a * function and all nested functions. @@ -239,17 +258,21 @@ function collectTemporariesSidemapImpl( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: Map, - isInnerFn: boolean, + innerFnContext: {instrId: InstructionId} | null, ): void { for (const [_, block] of fn.body.blocks) { - for (const instr of block.instructions) { - const {value, lvalue} = instr; + for (const {value, lvalue, id: origInstrId} of block.instructions) { + const instrId = + innerFnContext != null ? innerFnContext.instrId : origInstrId; const usedOutside = usedOutsideDeclaringScope.has( lvalue.identifier.declarationId, ); if (value.kind === 'PropertyLoad' && !usedOutside) { - if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + if ( + innerFnContext == null || + 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 @@ -265,13 +288,13 @@ function collectTemporariesSidemapImpl( temporaries.set(lvalue.identifier.id, property); } } else if ( - value.kind === 'LoadLocal' && + (value.kind === 'LoadLocal' || isLoadContextMutable(value, instrId)) && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { if ( - !isInnerFn || + innerFnContext == null || fn.context.some( context => context.identifier.id === value.place.identifier.id, ) @@ -289,7 +312,7 @@ function collectTemporariesSidemapImpl( value.loweredFunc.func, usedOutsideDeclaringScope, temporaries, - true, + innerFnContext ?? {instrId}, ); } } @@ -364,7 +387,7 @@ class Context { * Tracks the traversal state. See Context.declare for explanation of why this * is needed. */ - inInnerFn: boolean = false; + #innerFnContext: {outerInstrId: InstructionId} | null = null; constructor( temporariesUsedOutsideScope: ReadonlySet, @@ -434,7 +457,7 @@ class Context { * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { - if (this.inInnerFn) return; + if (this.#innerFnContext != null) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -577,11 +600,14 @@ class Context { currentScope.reassignments.add(place.identifier); } } - enterInnerFn(cb: () => T): T { - const wasInInnerFn = this.inInnerFn; - this.inInnerFn = true; + enterInnerFn( + innerFn: TInstruction | TInstruction, + cb: () => T, + ): T { + const prevContext = this.#innerFnContext; + this.#innerFnContext = this.#innerFnContext ?? {outerInstrId: innerFn.id}; const result = cb(); - this.inInnerFn = wasInInnerFn; + this.#innerFnContext = prevContext; return result; } @@ -724,9 +750,14 @@ function collectDependencies( * Recursively visit the inner function to extract dependencies there */ const innerFn = instr.value.loweredFunc.func; - context.enterInnerFn(() => { - handleFunction(innerFn); - }); + context.enterInnerFn( + instr as + | TInstruction + | TInstruction, + () => { + handleFunction(innerFn); + }, + ); } else { handleInstruction(instr, context); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md index 2b0031b117..f8712ed728 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md @@ -58,18 +58,16 @@ function Foo(t0) { bar = $[1]; result = $[2]; } - - const t1 = bar; - let t2; - if ($[3] !== result || $[4] !== t1) { - t2 = ; - $[3] = result; - $[4] = t1; - $[5] = t2; + let t1; + if ($[3] !== bar || $[4] !== result) { + t1 = ; + $[3] = bar; + $[4] = result; + $[5] = t1; } else { - t2 = $[5]; + t1 = $[5]; } - return t2; + return t1; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md index 7febb3fecb..1268cbcfdc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md @@ -43,16 +43,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = { x: t0 }; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = { x }; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md index 26b56ea2a4..769e4871f4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md @@ -42,16 +42,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 =
{t0}
; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 =
{x}
; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md index 5ffa73389f..e66ef2df13 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md @@ -43,16 +43,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = { x: t0 }; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = { x }; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md index 2c495d8223..66799c5c47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md @@ -42,16 +42,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = { x: t0 }; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = { x }; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { 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..d34db46d6a 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 @@ -33,17 +33,15 @@ function f(a) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 =
; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 =
; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { 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 deleted file mode 100644 index ae44f27912..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,53 +0,0 @@ - -## 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/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 new file mode 100644 index 0000000000..a1cbe89a88 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,101 @@ + +## 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}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @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) { + 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]; + } + let t0; + if ($[2] !== contextVar.val) { + t0 = () => [contextVar.val]; + $[2] = contextVar.val; + $[3] = t0; + } else { + t0 = $[3]; + } + contextVar; + const cb = t0; + let t1; + if ($[4] !== cb) { + t1 = ; + $[4] = cb; + $[5] = t1; + } else { + t1 = $[5]; + } + return t1; +} + +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/error.todo-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 similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx rename to 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/__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..e8a3e2d627 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 @@ -44,16 +44,15 @@ function useFoo(arr1, arr2) { y = $[2]; } let t0; - const t1 = y; - let t2; - if ($[3] !== t1) { - t2 = { y: t1 }; - $[3] = t1; - $[4] = t2; + let t1; + if ($[3] !== y) { + t1 = { y }; + $[3] = y; + $[4] = t1; } else { - t2 = $[4]; + t1 = $[4]; } - t0 = t2; + t0 = t1; return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md index 39f301432e..9d232d8e78 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md @@ -36,17 +36,15 @@ function HomeDiscoStoreItemTileRating(props) { } else { count = $[1]; } - - const t0 = count; - let t1; - if ($[2] !== t0) { - t1 = {t0}; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== count) { + t0 = {count}; + $[2] = count; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md index 23cc7ee846..ceaa350012 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md @@ -67,17 +67,15 @@ function Component(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = [t0]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md new file mode 100644 index 0000000000..d72f34b4fd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md @@ -0,0 +1,130 @@ + +## Input + +```javascript +import {throwErrorWithMessage, ValidateMemoization} from 'shared-runtime'; + +/** + * Context variables are local variables that (1) have at least one reassignment + * and (2) are captured into a function expression. These have a known mutable + * range: from first declaration / assignment to the last direct or aliased, + * mutable reference. + * + * This fixture validates that forget can take granular dependencies on context + * variables when the reference to a context var happens *after* the end of its + * mutable range. + */ +function Component({cond, a}) { + let contextVar; + if (cond) { + contextVar = {val: a}; + } else { + contextVar = {}; + throwErrorWithMessage(''); + } + const cb = {cb: () => contextVar.val * 4}; + + /** + * manually specify input to avoid adding a `PropertyLoad` from contextVar, + * which might affect hoistable-objects analysis. + */ + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: false, a: undefined}], + sequentialRenders: [ + {cond: true, a: 2}, + {cond: true, a: 2}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { throwErrorWithMessage, ValidateMemoization } from "shared-runtime"; + +/** + * Context variables are local variables that (1) have at least one reassignment + * and (2) are captured into a function expression. These have a known mutable + * range: from first declaration / assignment to the last direct or aliased, + * mutable reference. + * + * This fixture validates that forget can take granular dependencies on context + * variables when the reference to a context var happens *after* the end of its + * mutable range. + */ +function Component(t0) { + const $ = _c(10); + const { cond, a } = t0; + let contextVar; + if ($[0] !== a || $[1] !== cond) { + if (cond) { + contextVar = { val: a }; + } else { + contextVar = {}; + throwErrorWithMessage(""); + } + $[0] = a; + $[1] = cond; + $[2] = contextVar; + } else { + contextVar = $[2]; + } + let t1; + if ($[3] !== contextVar.val) { + t1 = { cb: () => contextVar.val * 4 }; + $[3] = contextVar.val; + $[4] = t1; + } else { + t1 = $[4]; + } + const cb = t1; + + const t2 = cond ? a : undefined; + let t3; + if ($[5] !== t2) { + t3 = [t2]; + $[5] = t2; + $[6] = t3; + } else { + t3 = $[6]; + } + let t4; + if ($[7] !== cb || $[8] !== t3) { + t4 = ( + + ); + $[7] = cb; + $[8] = t3; + $[9] = t4; + } else { + t4 = $[9]; + } + return t4; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: false, a: undefined }], + sequentialRenders: [ + { cond: true, a: 2 }, + { cond: true, a: 2 }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[2],"output":{"cb":"[[ function params=0 ]]"}}
+
{"inputs":[2],"output":{"cb":"[[ function params=0 ]]"}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js new file mode 100644 index 0000000000..b9bdd67e2f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js @@ -0,0 +1,43 @@ +import {throwErrorWithMessage, ValidateMemoization} from 'shared-runtime'; + +/** + * Context variables are local variables that (1) have at least one reassignment + * and (2) are captured into a function expression. These have a known mutable + * range: from first declaration / assignment to the last direct or aliased, + * mutable reference. + * + * This fixture validates that forget can take granular dependencies on context + * variables when the reference to a context var happens *after* the end of its + * mutable range. + */ +function Component({cond, a}) { + let contextVar; + if (cond) { + contextVar = {val: a}; + } else { + contextVar = {}; + throwErrorWithMessage(''); + } + const cb = {cb: () => contextVar.val * 4}; + + /** + * manually specify input to avoid adding a `PropertyLoad` from contextVar, + * which might affect hoistable-objects analysis. + */ + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: false, a: undefined}], + sequentialRenders: [ + {cond: true, a: 2}, + {cond: true, a: 2}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md index d8e59c486a..b7c425ba5c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md @@ -35,17 +35,15 @@ function HomeDiscoStoreItemTileRating(props) { } else { count = $[1]; } - - const t0 = count; - let t1; - if ($[2] !== t0) { - t1 = {t0}; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== count) { + t0 = {count}; + $[2] = count; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } ``` 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 d94a5e7e37..e335273026 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 @@ -88,36 +88,34 @@ function Inner(props) { input; input; let t0; - const t1 = input; - let t2; - if ($[0] !== t1) { - t2 = [t1]; - $[0] = t1; - $[1] = t2; + let t1; + if ($[0] !== input) { + t1 = [input]; + $[0] = input; + $[1] = t1; } else { - t2 = $[1]; + t1 = $[1]; } - t0 = t2; + t0 = t1; const output = t0; - const t3 = input; - let t4; - if ($[2] !== t3) { - t4 = [t3]; - $[2] = t3; - $[3] = t4; + let t2; + if ($[2] !== input) { + t2 = [input]; + $[2] = input; + $[3] = t2; } else { - t4 = $[3]; + t2 = $[3]; } - let t5; - if ($[4] !== output || $[5] !== t4) { - t5 = ; + let t3; + if ($[4] !== output || $[5] !== t2) { + t3 = ; $[4] = output; - $[5] = t4; - $[6] = t5; + $[5] = t2; + $[6] = t3; } else { - t5 = $[6]; + t3 = $[6]; } - return t5; + return t3; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/snap/src/sprout/index.ts b/compiler/packages/snap/src/sprout/index.ts index 733be561c0..04748bed28 100644 --- a/compiler/packages/snap/src/sprout/index.ts +++ b/compiler/packages/snap/src/sprout/index.ts @@ -32,7 +32,15 @@ export function runSprout( originalCode: string, forgetCode: string, ): SproutResult { - const forgetResult = doEval(forgetCode); + let forgetResult; + try { + (globalThis as any).__SNAP_EVALUATOR_MODE = 'forget'; + forgetResult = doEval(forgetCode); + } catch (e) { + throw e; + } finally { + (globalThis as any).__SNAP_EVALUATOR_MODE = undefined; + } if (forgetResult.kind === 'UnexpectedError') { return makeError('Unexpected error in Forget runner', forgetResult.value); } diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 58815842cb..1b8648f4ff 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -259,26 +259,35 @@ export function Throw() { export function ValidateMemoization({ inputs, - output, + output: rawOutput, + onlyCheckCompiled = false, }: { inputs: Array; output: any; + onlyCheckCompiled: boolean; }): React.ReactElement { 'use no forget'; + // Wrap rawOutput as it might be a function, which useState would invoke. + const output = {value: rawOutput}; const [previousInputs, setPreviousInputs] = React.useState(inputs); const [previousOutput, setPreviousOutput] = React.useState(output); if ( - inputs.length !== previousInputs.length || - inputs.some((item, i) => item !== previousInputs[i]) + onlyCheckCompiled && + (globalThis as any).__SNAP_EVALUATOR_MODE === 'forget' ) { - // Some input changed, we expect the output to change - setPreviousInputs(inputs); - setPreviousOutput(output); - } else if (output !== previousOutput) { - // Else output should be stable - throw new Error('Output identity changed but inputs did not'); + if ( + inputs.length !== previousInputs.length || + inputs.some((item, i) => item !== previousInputs[i]) + ) { + // Some input changed, we expect the output to change + setPreviousInputs(inputs); + setPreviousOutput(output); + } else if (output.value !== previousOutput.value) { + // Else output should be stable + throw new Error('Output identity changed but inputs did not'); + } } - return React.createElement(Stringify, {inputs, output}); + return React.createElement(Stringify, {inputs, output: rawOutput}); } export function createHookWrapper( From e68a6485e8ff2ba8169a5d92436470da547f74fc Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 14:06:37 -0500 Subject: [PATCH 130/422] [compiler][ez] Add shape for global Object.keys Add shape / type for global Object.keys. This is useful because - it has an Effect.Read (not an Effect.Capture) as it cannot alias its argument. - Object.keys return an array --- .../src/HIR/Globals.ts | 15 ++++++ .../compiler/shapes-object-key.expect.md | 49 +++++++++++++++++++ .../fixtures/compiler/shapes-object-key.ts | 11 +++++ 3 files changed, 75 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts index 2525b87bd8..c85de65f06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -87,6 +87,21 @@ const UNTYPED_GLOBALS: Set = new Set([ ]); const TYPED_GLOBALS: Array<[string, BuiltInType]> = [ + [ + 'Object', + addObject(DEFAULT_SHAPES, 'Object', [ + [ + 'keys', + addFunction(DEFAULT_SHAPES, [], { + positionalParams: [Effect.Read], + restParam: null, + returnType: {kind: 'Object', shapeId: BuiltInArrayId}, + calleeEffect: Effect.Read, + returnValueKind: ValueKind.Mutable, + }), + ], + ]), + ], [ 'Array', addObject(DEFAULT_SHAPES, 'Array', [ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md new file mode 100644 index 0000000000..e491eb6c69 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md @@ -0,0 +1,49 @@ + +## Input + +```javascript +import {arrayPush} from 'shared-runtime'; + +function useFoo({a, b}) { + const obj = {a}; + arrayPush(Object.keys(obj), b); + return obj; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2, b: 3}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { arrayPush } from "shared-runtime"; + +function useFoo(t0) { + const $ = _c(2); + const { a, b } = t0; + let t1; + if ($[0] !== a) { + t1 = { a }; + $[0] = a; + $[1] = t1; + } else { + t1 = $[1]; + } + const obj = t1; + arrayPush(Object.keys(obj), b); + return obj; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: 2, b: 3 }], +}; + +``` + +### Eval output +(kind: ok) {"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts new file mode 100644 index 0000000000..9dbaac79c6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts @@ -0,0 +1,11 @@ +import {arrayPush} from 'shared-runtime'; + +function useFoo({a, b}) { + const obj = {a}; + arrayPush(Object.keys(obj), b); + return obj; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2, b: 3}], +}; From 2837fe85fc6a1e261dd33ea9cdb06f2e5f98b544 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 13:48:29 -0500 Subject: [PATCH 131/422] [compiler][be] Logger based debug printing in test runner Avoid mutable logging enabled state and writing to `process.stdout` within our babel transform. --- .../babel-plugin-react-compiler/package.json | 2 - .../src/Entrypoint/Pipeline.ts | 44 +------ .../src/Inference/AnalyseFunctions.ts | 7 +- .../InferReactiveScopeVariables.ts | 7 +- .../src/Utils/logger.ts | 110 ------------------ compiler/packages/snap/src/compiler.ts | 22 ++-- compiler/packages/snap/src/constants.ts | 12 +- compiler/packages/snap/src/runner-worker.ts | 47 +++++++- 8 files changed, 78 insertions(+), 173 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts diff --git a/compiler/packages/babel-plugin-react-compiler/package.json b/compiler/packages/babel-plugin-react-compiler/package.json index 7d55e7b27f..158b800dba 100644 --- a/compiler/packages/babel-plugin-react-compiler/package.json +++ b/compiler/packages/babel-plugin-react-compiler/package.json @@ -42,9 +42,7 @@ "babel-jest": "^29.0.3", "babel-plugin-fbt": "^1.0.0", "babel-plugin-fbt-runtime": "^1.0.0", - "chalk": "4", "eslint": "^8.57.1", - "glob": "^7.1.6", "invariant": "^2.2.4", "jest": "^29.0.3", "jest-environment-jsdom": "^29.0.3", 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 6995aec7f1..c48cba32b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -79,13 +79,6 @@ import { rewriteInstructionKindsBasedOnReassignment, } from '../SSA'; import {inferTypes} from '../TypeInference'; -import { - logCodegenFunction, - logDebug, - logHIRFunction, - logReactiveFunction, -} from '../Utils/logger'; -import {assertExhaustive} from '../Utils/utils'; import { validateContextVariableLValues, validateHooksUsage, @@ -139,13 +132,7 @@ function run( name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - printLog({ - kind: 'debug', - name: 'EnvironmentConfig', - value: prettyFormat(env.config), - }); - const ast = runWithEnvironment(func, env); - return ast; + return runWithEnvironment(func, env); } /* @@ -158,10 +145,8 @@ function runWithEnvironment( >, env: Environment, ): CodegenFunction { - const log = (value: CompilerPipelineValue): CompilerPipelineValue => { - printLog(value); + const log = (value: CompilerPipelineValue): void => { env.logger?.debugLogIRs?.(value); - return value; }; const hir = lower(func, env).unwrap(); log({kind: 'hir', name: 'HIR', value: hir}); @@ -545,28 +530,3 @@ export function compileFn( code, ); } - -function printLog(value: CompilerPipelineValue): CompilerPipelineValue { - switch (value.kind) { - case 'ast': { - logCodegenFunction(value.name, value.value); - break; - } - case 'hir': { - logHIRFunction(value.name, value.value); - break; - } - case 'reactive': { - logReactiveFunction(value.name, value.value); - break; - } - case 'debug': { - logDebug(value.name, value.value); - break; - } - default: { - assertExhaustive(value, 'Unexpected compilation kind'); - } - } - return value; -} 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..75bd8f6811 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -19,7 +19,6 @@ import { import {deadCodeElimination} from '../Optimization'; import {inferReactiveScopeVariables} from '../ReactiveScopes'; import {rewriteInstructionKindsBasedOnReassignment} from '../SSA'; -import {logHIRFunction} from '../Utils/logger'; import {inferMutableContextVariables} from './InferMutableContextVariables'; import {inferMutableRanges} from './InferMutableRanges'; import inferReferenceEffects from './InferReferenceEffects'; @@ -112,7 +111,11 @@ function lower(func: HIRFunction): void { rewriteInstructionKindsBasedOnReassignment(func); inferReactiveScopeVariables(func); inferMutableContextVariables(func); - logHIRFunction('AnalyseFunction (inner)', func); + func.env.logger?.debugLogIRs?.({ + kind: 'hir', + name: 'AnalyseFunction (inner)', + value: func, + }); } function infer( diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts index 098139b150..1108422f07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -25,7 +25,6 @@ import { eachPatternOperand, } from '../HIR/visitors'; import DisjointSet from '../Utils/DisjointSet'; -import {logHIRFunction} from '../Utils/logger'; import {assertExhaustive} from '../Utils/utils'; /* @@ -156,7 +155,11 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void { scope.range.end > maxInstruction + 1 ) { // Make it easier to debug why the error occurred - logHIRFunction('InferReactiveScopeVariables (invalid scope)', fn); + fn.env.logger?.debugLogIRs?.({ + kind: 'hir', + name: 'InferReactiveScopeVariables (invalid scope)', + value: fn, + }); CompilerError.invariant(false, { reason: `Invalid mutable range for scope`, loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts deleted file mode 100644 index fa43a8befe..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts +++ /dev/null @@ -1,110 +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 generate from '@babel/generator'; -import * as t from '@babel/types'; -import chalk from 'chalk'; -import {HIR, HIRFunction, ReactiveFunction} from '../HIR/HIR'; -import {printFunctionWithOutlined, printHIR} from '../HIR/PrintHIR'; -import {CodegenFunction} from '../ReactiveScopes'; -import {printReactiveFunctionWithOutlined} from '../ReactiveScopes/PrintReactiveFunction'; - -let ENABLED: boolean = false; - -let lastLogged: string; - -export function toggleLogging(enabled: boolean): void { - ENABLED = enabled; -} - -export function logDebug(step: string, value: string): void { - if (ENABLED) { - process.stdout.write(`${chalk.green(step)}:\n${value}\n\n`); - } -} - -export function logHIR(step: string, ir: HIR): void { - if (ENABLED) { - const printed = printHIR(ir); - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function logCodegenFunction(step: string, fn: CodegenFunction): void { - if (ENABLED) { - let printed: string | null = null; - try { - const node = t.functionDeclaration( - fn.id, - fn.params, - fn.body, - fn.generator, - fn.async, - ); - const ast = generate(node); - printed = ast.code; - } catch (e) { - let errMsg: string; - if ( - typeof e === 'object' && - e != null && - 'message' in e && - typeof e.message === 'string' - ) { - errMsg = e.message.toString(); - } else { - errMsg = '[empty]'; - } - console.log('Error formatting AST: ' + errMsg); - } - if (printed === null) { - return; - } - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function logHIRFunction(step: string, fn: HIRFunction): void { - if (ENABLED) { - const printed = printFunctionWithOutlined(fn); - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function logReactiveFunction(step: string, fn: ReactiveFunction): void { - if (ENABLED) { - const printed = printReactiveFunctionWithOutlined(fn); - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function log(fn: () => string): void { - if (ENABLED) { - const message = fn(); - process.stdout.write(message.trim() + '\n\n'); - } -} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 1cb8fe48b9..5866445d2e 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -19,6 +19,7 @@ import type { PanicThresholdOptions, PluginOptions, CompilerReactTarget, + CompilerPipelineValue, } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; import type { @@ -45,6 +46,7 @@ export function parseLanguage(source: string): 'flow' | 'typescript' { function makePluginOptions( firstLine: string, parseConfigPragmaFn: typeof ParseConfigPragma, + debugIRLogger: (value: CompilerPipelineValue) => void, EffectEnum: typeof Effect, ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { @@ -182,15 +184,15 @@ function makePluginOptions( .filter(s => s.length > 0); } - let logs: Array<{filename: string | null; event: LoggerEvent}> = []; - let logger: Logger | null = null; - if (firstLine.includes('@logger')) { - logger = { - logEvent(filename: string | null, event: LoggerEvent): void { - logs.push({filename, event}); - }, - }; - } + const logs: Array<{filename: string | null; event: LoggerEvent}> = []; + const logger: Logger = { + logEvent: firstLine.includes('@logger') + ? (filename, event) => { + logs.push({filename, event}); + } + : () => {}, + debugLogIRs: debugIRLogger, + }; const config = parseConfigPragmaFn(firstLine); const options = { @@ -338,6 +340,7 @@ export async function transformFixtureInput( parseConfigPragmaFn: typeof ParseConfigPragma, plugin: BabelCore.PluginObj, includeEvaluator: boolean, + debugIRLogger: (value: CompilerPipelineValue) => void, EffectEnum: typeof Effect, ValueKindEnum: typeof ValueKind, ): Promise<{kind: 'ok'; value: TransformResult} | {kind: 'err'; msg: string}> { @@ -365,6 +368,7 @@ export async function transformFixtureInput( const [options, logs] = makePluginOptions( firstLine, parseConfigPragmaFn, + debugIRLogger, EffectEnum, ValueKindEnum, ); diff --git a/compiler/packages/snap/src/constants.ts b/compiler/packages/snap/src/constants.ts index abee06c55b..ad77441b53 100644 --- a/compiler/packages/snap/src/constants.ts +++ b/compiler/packages/snap/src/constants.ts @@ -18,11 +18,17 @@ export const COMPILER_PATH = path.join( 'BabelPlugin.js', ); export const COMPILER_INDEX_PATH = path.join(process.cwd(), 'dist', 'index'); -export const LOGGER_PATH = path.join( +export const PRINT_HIR_PATH = path.join( process.cwd(), 'dist', - 'Utils', - 'logger.js', + 'HIR', + 'PrintHIR.js', +); +export const PRINT_REACTIVE_IR_PATH = path.join( + process.cwd(), + 'dist', + 'ReactiveScopes', + 'PrintReactiveFunction.js', ); export const PARSE_CONFIG_PRAGMA_PATH = path.join( process.cwd(), diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index f05757d3df..ea87cd1e91 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -8,16 +8,21 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {PluginObj} from '@babel/core'; import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import type {printFunctionWithOutlined as PrintFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; +import type {printReactiveFunctionWithOutlined as PrintReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; import {TransformResult, transformFixtureInput} from './compiler'; import { COMPILER_PATH, COMPILER_INDEX_PATH, - LOGGER_PATH, PARSE_CONFIG_PRAGMA_PATH, + PRINT_HIR_PATH, + PRINT_REACTIVE_IR_PATH, } from './constants'; import {TestFixture, getBasename, isExpectError} from './fixture-utils'; import {TestResult, writeOutputToString} from './reporter'; import {runSprout} from './sprout'; +import {CompilerPipelineValue} from 'babel-plugin-react-compiler/src'; +import chalk from 'chalk'; const originalConsoleError = console.error; @@ -64,20 +69,56 @@ async function compile( const {Effect: EffectEnum, ValueKind: ValueKindEnum} = require( COMPILER_INDEX_PATH, ); - const {toggleLogging} = require(LOGGER_PATH); + const {printFunctionWithOutlined} = require(PRINT_HIR_PATH) as { + printFunctionWithOutlined: typeof PrintFunctionWithOutlined; + }; + const {printReactiveFunctionWithOutlined} = require( + PRINT_REACTIVE_IR_PATH, + ) as { + printReactiveFunctionWithOutlined: typeof PrintReactiveFunctionWithOutlined; + }; + + let lastLogged: string | null = null; + const debugIRLogger = shouldLog + ? (value: CompilerPipelineValue) => { + let printed: string; + switch (value.kind) { + case 'hir': + printed = printFunctionWithOutlined(value.value); + break; + case 'reactive': + printed = printReactiveFunctionWithOutlined(value.value); + break; + case 'debug': + printed = value.value; + break; + case 'ast': + // skip printing ast as we already write fixture output JS + printed = '(ast)'; + break; + } + + if (printed !== lastLogged) { + lastLogged = printed; + console.log(`${chalk.green(value.name)}:\n ${printed}\n`); + } else { + console.log(`${chalk.blue(value.name)}: (no change)\n`); + } + } + : () => {}; const {parseConfigPragmaForTests} = require(PARSE_CONFIG_PRAGMA_PATH) as { parseConfigPragmaForTests: typeof ParseConfigPragma; }; // only try logging if we filtered out all but one fixture, // since console log order is non-deterministic - toggleLogging(shouldLog); const result = await transformFixtureInput( input, fixturePath, parseConfigPragmaForTests, BabelPluginReactCompiler, includeEvaluator, + debugIRLogger, EffectEnum, ValueKindEnum, ); From 2764403f05f50f10efcce63227167056c949d646 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 15:10:32 -0500 Subject: [PATCH 132/422] [compiler][be] Logger based debug printing in test runner Avoid mutable logging enabled state and writing to `process.stdout` within our babel transform. --- .../babel-plugin-react-compiler/package.json | 2 - .../src/Entrypoint/Pipeline.ts | 44 +------ .../src/Inference/AnalyseFunctions.ts | 7 +- .../InferReactiveScopeVariables.ts | 7 +- .../src/Utils/logger.ts | 110 ------------------ compiler/packages/snap/src/compiler.ts | 22 ++-- compiler/packages/snap/src/constants.ts | 12 +- compiler/packages/snap/src/runner-worker.ts | 47 +++++++- 8 files changed, 78 insertions(+), 173 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts diff --git a/compiler/packages/babel-plugin-react-compiler/package.json b/compiler/packages/babel-plugin-react-compiler/package.json index 7d55e7b27f..158b800dba 100644 --- a/compiler/packages/babel-plugin-react-compiler/package.json +++ b/compiler/packages/babel-plugin-react-compiler/package.json @@ -42,9 +42,7 @@ "babel-jest": "^29.0.3", "babel-plugin-fbt": "^1.0.0", "babel-plugin-fbt-runtime": "^1.0.0", - "chalk": "4", "eslint": "^8.57.1", - "glob": "^7.1.6", "invariant": "^2.2.4", "jest": "^29.0.3", "jest-environment-jsdom": "^29.0.3", 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 6995aec7f1..c48cba32b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -79,13 +79,6 @@ import { rewriteInstructionKindsBasedOnReassignment, } from '../SSA'; import {inferTypes} from '../TypeInference'; -import { - logCodegenFunction, - logDebug, - logHIRFunction, - logReactiveFunction, -} from '../Utils/logger'; -import {assertExhaustive} from '../Utils/utils'; import { validateContextVariableLValues, validateHooksUsage, @@ -139,13 +132,7 @@ function run( name: 'EnvironmentConfig', value: prettyFormat(env.config), }); - printLog({ - kind: 'debug', - name: 'EnvironmentConfig', - value: prettyFormat(env.config), - }); - const ast = runWithEnvironment(func, env); - return ast; + return runWithEnvironment(func, env); } /* @@ -158,10 +145,8 @@ function runWithEnvironment( >, env: Environment, ): CodegenFunction { - const log = (value: CompilerPipelineValue): CompilerPipelineValue => { - printLog(value); + const log = (value: CompilerPipelineValue): void => { env.logger?.debugLogIRs?.(value); - return value; }; const hir = lower(func, env).unwrap(); log({kind: 'hir', name: 'HIR', value: hir}); @@ -545,28 +530,3 @@ export function compileFn( code, ); } - -function printLog(value: CompilerPipelineValue): CompilerPipelineValue { - switch (value.kind) { - case 'ast': { - logCodegenFunction(value.name, value.value); - break; - } - case 'hir': { - logHIRFunction(value.name, value.value); - break; - } - case 'reactive': { - logReactiveFunction(value.name, value.value); - break; - } - case 'debug': { - logDebug(value.name, value.value); - break; - } - default: { - assertExhaustive(value, 'Unexpected compilation kind'); - } - } - return value; -} 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..75bd8f6811 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -19,7 +19,6 @@ import { import {deadCodeElimination} from '../Optimization'; import {inferReactiveScopeVariables} from '../ReactiveScopes'; import {rewriteInstructionKindsBasedOnReassignment} from '../SSA'; -import {logHIRFunction} from '../Utils/logger'; import {inferMutableContextVariables} from './InferMutableContextVariables'; import {inferMutableRanges} from './InferMutableRanges'; import inferReferenceEffects from './InferReferenceEffects'; @@ -112,7 +111,11 @@ function lower(func: HIRFunction): void { rewriteInstructionKindsBasedOnReassignment(func); inferReactiveScopeVariables(func); inferMutableContextVariables(func); - logHIRFunction('AnalyseFunction (inner)', func); + func.env.logger?.debugLogIRs?.({ + kind: 'hir', + name: 'AnalyseFunction (inner)', + value: func, + }); } function infer( diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts index 098139b150..1108422f07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -25,7 +25,6 @@ import { eachPatternOperand, } from '../HIR/visitors'; import DisjointSet from '../Utils/DisjointSet'; -import {logHIRFunction} from '../Utils/logger'; import {assertExhaustive} from '../Utils/utils'; /* @@ -156,7 +155,11 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void { scope.range.end > maxInstruction + 1 ) { // Make it easier to debug why the error occurred - logHIRFunction('InferReactiveScopeVariables (invalid scope)', fn); + fn.env.logger?.debugLogIRs?.({ + kind: 'hir', + name: 'InferReactiveScopeVariables (invalid scope)', + value: fn, + }); CompilerError.invariant(false, { reason: `Invalid mutable range for scope`, loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts deleted file mode 100644 index fa43a8befe..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/logger.ts +++ /dev/null @@ -1,110 +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 generate from '@babel/generator'; -import * as t from '@babel/types'; -import chalk from 'chalk'; -import {HIR, HIRFunction, ReactiveFunction} from '../HIR/HIR'; -import {printFunctionWithOutlined, printHIR} from '../HIR/PrintHIR'; -import {CodegenFunction} from '../ReactiveScopes'; -import {printReactiveFunctionWithOutlined} from '../ReactiveScopes/PrintReactiveFunction'; - -let ENABLED: boolean = false; - -let lastLogged: string; - -export function toggleLogging(enabled: boolean): void { - ENABLED = enabled; -} - -export function logDebug(step: string, value: string): void { - if (ENABLED) { - process.stdout.write(`${chalk.green(step)}:\n${value}\n\n`); - } -} - -export function logHIR(step: string, ir: HIR): void { - if (ENABLED) { - const printed = printHIR(ir); - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function logCodegenFunction(step: string, fn: CodegenFunction): void { - if (ENABLED) { - let printed: string | null = null; - try { - const node = t.functionDeclaration( - fn.id, - fn.params, - fn.body, - fn.generator, - fn.async, - ); - const ast = generate(node); - printed = ast.code; - } catch (e) { - let errMsg: string; - if ( - typeof e === 'object' && - e != null && - 'message' in e && - typeof e.message === 'string' - ) { - errMsg = e.message.toString(); - } else { - errMsg = '[empty]'; - } - console.log('Error formatting AST: ' + errMsg); - } - if (printed === null) { - return; - } - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function logHIRFunction(step: string, fn: HIRFunction): void { - if (ENABLED) { - const printed = printFunctionWithOutlined(fn); - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function logReactiveFunction(step: string, fn: ReactiveFunction): void { - if (ENABLED) { - const printed = printReactiveFunctionWithOutlined(fn); - if (printed !== lastLogged) { - lastLogged = printed; - process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`); - } else { - process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`); - } - } -} - -export function log(fn: () => string): void { - if (ENABLED) { - const message = fn(); - process.stdout.write(message.trim() + '\n\n'); - } -} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 1cb8fe48b9..5866445d2e 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -19,6 +19,7 @@ import type { PanicThresholdOptions, PluginOptions, CompilerReactTarget, + CompilerPipelineValue, } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; import type { @@ -45,6 +46,7 @@ export function parseLanguage(source: string): 'flow' | 'typescript' { function makePluginOptions( firstLine: string, parseConfigPragmaFn: typeof ParseConfigPragma, + debugIRLogger: (value: CompilerPipelineValue) => void, EffectEnum: typeof Effect, ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { @@ -182,15 +184,15 @@ function makePluginOptions( .filter(s => s.length > 0); } - let logs: Array<{filename: string | null; event: LoggerEvent}> = []; - let logger: Logger | null = null; - if (firstLine.includes('@logger')) { - logger = { - logEvent(filename: string | null, event: LoggerEvent): void { - logs.push({filename, event}); - }, - }; - } + const logs: Array<{filename: string | null; event: LoggerEvent}> = []; + const logger: Logger = { + logEvent: firstLine.includes('@logger') + ? (filename, event) => { + logs.push({filename, event}); + } + : () => {}, + debugLogIRs: debugIRLogger, + }; const config = parseConfigPragmaFn(firstLine); const options = { @@ -338,6 +340,7 @@ export async function transformFixtureInput( parseConfigPragmaFn: typeof ParseConfigPragma, plugin: BabelCore.PluginObj, includeEvaluator: boolean, + debugIRLogger: (value: CompilerPipelineValue) => void, EffectEnum: typeof Effect, ValueKindEnum: typeof ValueKind, ): Promise<{kind: 'ok'; value: TransformResult} | {kind: 'err'; msg: string}> { @@ -365,6 +368,7 @@ export async function transformFixtureInput( const [options, logs] = makePluginOptions( firstLine, parseConfigPragmaFn, + debugIRLogger, EffectEnum, ValueKindEnum, ); diff --git a/compiler/packages/snap/src/constants.ts b/compiler/packages/snap/src/constants.ts index abee06c55b..ad77441b53 100644 --- a/compiler/packages/snap/src/constants.ts +++ b/compiler/packages/snap/src/constants.ts @@ -18,11 +18,17 @@ export const COMPILER_PATH = path.join( 'BabelPlugin.js', ); export const COMPILER_INDEX_PATH = path.join(process.cwd(), 'dist', 'index'); -export const LOGGER_PATH = path.join( +export const PRINT_HIR_PATH = path.join( process.cwd(), 'dist', - 'Utils', - 'logger.js', + 'HIR', + 'PrintHIR.js', +); +export const PRINT_REACTIVE_IR_PATH = path.join( + process.cwd(), + 'dist', + 'ReactiveScopes', + 'PrintReactiveFunction.js', ); export const PARSE_CONFIG_PRAGMA_PATH = path.join( process.cwd(), diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index f05757d3df..ea87cd1e91 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -8,16 +8,21 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {PluginObj} from '@babel/core'; import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import type {printFunctionWithOutlined as PrintFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; +import type {printReactiveFunctionWithOutlined as PrintReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; import {TransformResult, transformFixtureInput} from './compiler'; import { COMPILER_PATH, COMPILER_INDEX_PATH, - LOGGER_PATH, PARSE_CONFIG_PRAGMA_PATH, + PRINT_HIR_PATH, + PRINT_REACTIVE_IR_PATH, } from './constants'; import {TestFixture, getBasename, isExpectError} from './fixture-utils'; import {TestResult, writeOutputToString} from './reporter'; import {runSprout} from './sprout'; +import {CompilerPipelineValue} from 'babel-plugin-react-compiler/src'; +import chalk from 'chalk'; const originalConsoleError = console.error; @@ -64,20 +69,56 @@ async function compile( const {Effect: EffectEnum, ValueKind: ValueKindEnum} = require( COMPILER_INDEX_PATH, ); - const {toggleLogging} = require(LOGGER_PATH); + const {printFunctionWithOutlined} = require(PRINT_HIR_PATH) as { + printFunctionWithOutlined: typeof PrintFunctionWithOutlined; + }; + const {printReactiveFunctionWithOutlined} = require( + PRINT_REACTIVE_IR_PATH, + ) as { + printReactiveFunctionWithOutlined: typeof PrintReactiveFunctionWithOutlined; + }; + + let lastLogged: string | null = null; + const debugIRLogger = shouldLog + ? (value: CompilerPipelineValue) => { + let printed: string; + switch (value.kind) { + case 'hir': + printed = printFunctionWithOutlined(value.value); + break; + case 'reactive': + printed = printReactiveFunctionWithOutlined(value.value); + break; + case 'debug': + printed = value.value; + break; + case 'ast': + // skip printing ast as we already write fixture output JS + printed = '(ast)'; + break; + } + + if (printed !== lastLogged) { + lastLogged = printed; + console.log(`${chalk.green(value.name)}:\n ${printed}\n`); + } else { + console.log(`${chalk.blue(value.name)}: (no change)\n`); + } + } + : () => {}; const {parseConfigPragmaForTests} = require(PARSE_CONFIG_PRAGMA_PATH) as { parseConfigPragmaForTests: typeof ParseConfigPragma; }; // only try logging if we filtered out all but one fixture, // since console log order is non-deterministic - toggleLogging(shouldLog); const result = await transformFixtureInput( input, fixturePath, parseConfigPragmaForTests, BabelPluginReactCompiler, includeEvaluator, + debugIRLogger, EffectEnum, ValueKindEnum, ); From 9bb198d5c46ce94885f482ca956b70fe85a7d4fc Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 15:13:06 -0500 Subject: [PATCH 133/422] [compiler][be] Move inferEffectDeps test fixtures Test fixtures testing different compiler features (e.g. non-auto memoization) should live in separate directories --- .../{ => inferEffectDeps}/infer-deps-custom-config.expect.md | 0 .../compiler/{ => inferEffectDeps}/infer-deps-custom-config.js | 0 .../{ => inferEffectDeps}/infer-effect-dependencies.expect.md | 0 .../compiler/{ => inferEffectDeps}/infer-effect-dependencies.js | 0 .../compiler/{ => inferEffectDeps}/nonreactive-dep.expect.md | 0 .../fixtures/compiler/{ => inferEffectDeps}/nonreactive-dep.js | 0 .../{ => inferEffectDeps}/nonreactive-ref-helper.expect.md | 0 .../compiler/{ => inferEffectDeps}/nonreactive-ref-helper.js | 0 .../compiler/{ => inferEffectDeps}/nonreactive-ref.expect.md | 0 .../fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref.js | 0 .../compiler/{ => inferEffectDeps}/outlined-function.expect.md | 0 .../fixtures/compiler/{ => inferEffectDeps}/outlined-function.js | 0 .../{ => inferEffectDeps}/pruned-nonreactive-obj.expect.md | 0 .../compiler/{ => inferEffectDeps}/pruned-nonreactive-obj.js | 0 .../{ => inferEffectDeps}/reactive-memberexpr-merge.expect.md | 0 .../compiler/{ => inferEffectDeps}/reactive-memberexpr-merge.js | 0 .../compiler/{ => inferEffectDeps}/reactive-memberexpr.expect.md | 0 .../compiler/{ => inferEffectDeps}/reactive-memberexpr.js | 0 .../{ => inferEffectDeps}/reactive-optional-chain.expect.md | 0 .../compiler/{ => inferEffectDeps}/reactive-optional-chain.js | 0 .../compiler/{ => inferEffectDeps}/reactive-variable.expect.md | 0 .../fixtures/compiler/{ => inferEffectDeps}/reactive-variable.js | 0 .../todo-import-namespace-useEffect.expect.md | 0 .../{ => inferEffectDeps}/todo-import-namespace-useEffect.js | 0 24 files changed, 0 insertions(+), 0 deletions(-) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-deps-custom-config.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-deps-custom-config.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-effect-dependencies.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-effect-dependencies.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-dep.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-dep.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref-helper.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref-helper.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/outlined-function.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/outlined-function.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/pruned-nonreactive-obj.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/pruned-nonreactive-obj.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr-merge.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr-merge.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-optional-chain.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-optional-chain.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-variable.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-variable.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/todo-import-namespace-useEffect.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/todo-import-namespace-useEffect.js (100%) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.js From 8f72b9a530e97ca9f5662dc6749d6092a47f91ec Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 15:18:10 -0500 Subject: [PATCH 134/422] [compiler][be] Move inferEffectDeps test fixtures Test fixtures testing different compiler features (e.g. non-auto memoization) should live in separate directories --- .../{ => inferEffectDeps}/infer-deps-custom-config.expect.md | 0 .../compiler/{ => inferEffectDeps}/infer-deps-custom-config.js | 0 .../{ => inferEffectDeps}/infer-effect-dependencies.expect.md | 0 .../compiler/{ => inferEffectDeps}/infer-effect-dependencies.js | 0 .../compiler/{ => inferEffectDeps}/nonreactive-dep.expect.md | 0 .../fixtures/compiler/{ => inferEffectDeps}/nonreactive-dep.js | 0 .../{ => inferEffectDeps}/nonreactive-ref-helper.expect.md | 0 .../compiler/{ => inferEffectDeps}/nonreactive-ref-helper.js | 0 .../compiler/{ => inferEffectDeps}/nonreactive-ref.expect.md | 0 .../fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref.js | 0 .../compiler/{ => inferEffectDeps}/outlined-function.expect.md | 0 .../fixtures/compiler/{ => inferEffectDeps}/outlined-function.js | 0 .../{ => inferEffectDeps}/pruned-nonreactive-obj.expect.md | 0 .../compiler/{ => inferEffectDeps}/pruned-nonreactive-obj.js | 0 .../{ => inferEffectDeps}/reactive-memberexpr-merge.expect.md | 0 .../compiler/{ => inferEffectDeps}/reactive-memberexpr-merge.js | 0 .../compiler/{ => inferEffectDeps}/reactive-memberexpr.expect.md | 0 .../compiler/{ => inferEffectDeps}/reactive-memberexpr.js | 0 .../{ => inferEffectDeps}/reactive-optional-chain.expect.md | 0 .../compiler/{ => inferEffectDeps}/reactive-optional-chain.js | 0 .../compiler/{ => inferEffectDeps}/reactive-variable.expect.md | 0 .../fixtures/compiler/{ => inferEffectDeps}/reactive-variable.js | 0 .../todo-import-namespace-useEffect.expect.md | 0 .../{ => inferEffectDeps}/todo-import-namespace-useEffect.js | 0 24 files changed, 0 insertions(+), 0 deletions(-) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-deps-custom-config.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-deps-custom-config.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-effect-dependencies.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-effect-dependencies.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-dep.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-dep.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref-helper.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref-helper.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/outlined-function.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/outlined-function.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/pruned-nonreactive-obj.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/pruned-nonreactive-obj.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr-merge.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr-merge.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-optional-chain.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-optional-chain.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-variable.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-variable.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/todo-import-namespace-useEffect.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/todo-import-namespace-useEffect.js (100%) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.js From 53d1728bcf37d135f3842998709cbda9d2feadec Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 15:18:13 -0500 Subject: [PATCH 135/422] [compiler] Context variables as dependencies We previously didn't track context variables in the hoistable values sidemap of `propagateScopeDependencies`. This was overly conservative as we *do* track the mutable range of context variables, and it is safe to hoist accesses to context variables after their last direct / aliased maybe-assignment. ```js function Component({value}) { // start of mutable range for `x` let x = DEFAULT; const setX = () => x = value; const aliasedSet = maybeAlias(setX); maybeCall(aliasedSet); // end of mutable range for `x` // here, we should be able to take x (and property reads // off of x) as dependencies return } ``` --- .../src/HIR/HIR.ts | 11 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 65 ++++++--- .../bug-functiondecl-hoisting.expect.md | 18 ++- ...-array-assignment-to-context-var.expect.md | 15 +- ...array-declaration-to-context-var.expect.md | 15 +- ...object-assignment-to-context-var.expect.md | 15 +- ...bject-declaration-to-context-var.expect.md | 15 +- ...mutated-non-reactive-to-reactive.expect.md | 16 +-- ...ures-reassigned-context-property.expect.md | 53 ------- ...ures-reassigned-context-property.expect.md | 101 ++++++++++++++ ...-captures-reassigned-context-property.tsx} | 0 ...o-reordering-depslist-assignment.expect.md | 15 +- ...epro-scope-missing-mutable-range.expect.md | 16 +-- ...l-dependency-on-context-variable.expect.md | 16 +-- .../context-var-granular-dep.expect.md | 130 ++++++++++++++++++ .../context-var-granular-dep.js | 43 ++++++ ...epro-scope-missing-mutable-range.expect.md | 16 +-- .../use-operator-conditional.expect.md | 42 +++--- compiler/packages/snap/src/sprout/index.ts | 10 +- .../snap/src/sprout/shared-runtime.ts | 29 ++-- 20 files changed, 447 insertions(+), 194 deletions(-) delete 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/useCallback-captures-reassigned-context-property.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{error.todo-useCallback-captures-reassigned-context-property.tsx => useCallback-captures-reassigned-context-property.tsx} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js 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 475d8e8bec..a6a9cbcd48 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -840,6 +840,11 @@ export type LoadLocal = { place: Place; loc: SourceLocation; }; +export type LoadContext = { + kind: 'LoadContext'; + place: Place; + loc: SourceLocation; +}; /* * The value of a given instruction. Note that values are not recursive: complex @@ -852,11 +857,7 @@ export type LoadLocal = { export type InstructionValue = | LoadLocal - | { - kind: 'LoadContext'; - place: Place; - loc: SourceLocation; - } + | LoadContext | { kind: 'DeclareLocal'; lvalue: LValue; 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 4a85a4ef5c..08856e9143 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -17,6 +17,11 @@ import { areEqualPaths, IdentifierId, Terminal, + InstructionValue, + LoadContext, + TInstruction, + FunctionExpression, + ObjectMethod, } from './HIR'; import { collectHoistablePropertyLoads, @@ -223,11 +228,25 @@ export function collectTemporariesSidemap( fn, usedOutsideDeclaringScope, temporaries, - false, + null, ); return temporaries; } +function isLoadContextMutable( + instrValue: InstructionValue, + id: InstructionId, +): instrValue is LoadContext { + if (instrValue.kind === 'LoadContext') { + CompilerError.invariant(instrValue.place.identifier.scope != null, { + reason: + '[PropagateScopeDependencies] Expected all context variables to be assigned a scope', + loc: instrValue.loc, + }); + return id >= instrValue.place.identifier.scope.range.end; + } + return false; +} /** * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a * function and all nested functions. @@ -239,17 +258,21 @@ function collectTemporariesSidemapImpl( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: Map, - isInnerFn: boolean, + innerFnContext: {instrId: InstructionId} | null, ): void { for (const [_, block] of fn.body.blocks) { - for (const instr of block.instructions) { - const {value, lvalue} = instr; + for (const {value, lvalue, id: origInstrId} of block.instructions) { + const instrId = + innerFnContext != null ? innerFnContext.instrId : origInstrId; const usedOutside = usedOutsideDeclaringScope.has( lvalue.identifier.declarationId, ); if (value.kind === 'PropertyLoad' && !usedOutside) { - if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + if ( + innerFnContext == null || + 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 @@ -265,13 +288,13 @@ function collectTemporariesSidemapImpl( temporaries.set(lvalue.identifier.id, property); } } else if ( - value.kind === 'LoadLocal' && + (value.kind === 'LoadLocal' || isLoadContextMutable(value, instrId)) && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { if ( - !isInnerFn || + innerFnContext == null || fn.context.some( context => context.identifier.id === value.place.identifier.id, ) @@ -289,7 +312,7 @@ function collectTemporariesSidemapImpl( value.loweredFunc.func, usedOutsideDeclaringScope, temporaries, - true, + innerFnContext ?? {instrId}, ); } } @@ -364,7 +387,7 @@ class Context { * Tracks the traversal state. See Context.declare for explanation of why this * is needed. */ - inInnerFn: boolean = false; + #innerFnContext: {outerInstrId: InstructionId} | null = null; constructor( temporariesUsedOutsideScope: ReadonlySet, @@ -434,7 +457,7 @@ class Context { * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { - if (this.inInnerFn) return; + if (this.#innerFnContext != null) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -577,11 +600,14 @@ class Context { currentScope.reassignments.add(place.identifier); } } - enterInnerFn(cb: () => T): T { - const wasInInnerFn = this.inInnerFn; - this.inInnerFn = true; + enterInnerFn( + innerFn: TInstruction | TInstruction, + cb: () => T, + ): T { + const prevContext = this.#innerFnContext; + this.#innerFnContext = this.#innerFnContext ?? {outerInstrId: innerFn.id}; const result = cb(); - this.inInnerFn = wasInInnerFn; + this.#innerFnContext = prevContext; return result; } @@ -724,9 +750,14 @@ function collectDependencies( * Recursively visit the inner function to extract dependencies there */ const innerFn = instr.value.loweredFunc.func; - context.enterInnerFn(() => { - handleFunction(innerFn); - }); + context.enterInnerFn( + instr as + | TInstruction + | TInstruction, + () => { + handleFunction(innerFn); + }, + ); } else { handleInstruction(instr, context); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md index 2b0031b117..f8712ed728 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md @@ -58,18 +58,16 @@ function Foo(t0) { bar = $[1]; result = $[2]; } - - const t1 = bar; - let t2; - if ($[3] !== result || $[4] !== t1) { - t2 = ; - $[3] = result; - $[4] = t1; - $[5] = t2; + let t1; + if ($[3] !== bar || $[4] !== result) { + t1 = ; + $[3] = bar; + $[4] = result; + $[5] = t1; } else { - t2 = $[5]; + t1 = $[5]; } - return t2; + return t1; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md index 7febb3fecb..1268cbcfdc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-assignment-to-context-var.expect.md @@ -43,16 +43,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = { x: t0 }; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = { x }; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md index 26b56ea2a4..769e4871f4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md @@ -42,16 +42,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 =
{t0}
; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 =
{x}
; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md index 5ffa73389f..e66ef2df13 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-assignment-to-context-var.expect.md @@ -43,16 +43,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = { x: t0 }; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = { x }; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md index 2c495d8223..66799c5c47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-object-declaration-to-context-var.expect.md @@ -42,16 +42,15 @@ function Component(props) { } else { x = $[1]; } - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = { x: t0 }; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = { x }; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { 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..d34db46d6a 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 @@ -33,17 +33,15 @@ function f(a) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 =
; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 =
; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { 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 deleted file mode 100644 index ae44f27912..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,53 +0,0 @@ - -## 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/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 new file mode 100644 index 0000000000..a1cbe89a88 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,101 @@ + +## 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}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @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) { + 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]; + } + let t0; + if ($[2] !== contextVar.val) { + t0 = () => [contextVar.val]; + $[2] = contextVar.val; + $[3] = t0; + } else { + t0 = $[3]; + } + contextVar; + const cb = t0; + let t1; + if ($[4] !== cb) { + t1 = ; + $[4] = cb; + $[5] = t1; + } else { + t1 = $[5]; + } + return t1; +} + +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/error.todo-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 similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx rename to 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/__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..e8a3e2d627 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 @@ -44,16 +44,15 @@ function useFoo(arr1, arr2) { y = $[2]; } let t0; - const t1 = y; - let t2; - if ($[3] !== t1) { - t2 = { y: t1 }; - $[3] = t1; - $[4] = t2; + let t1; + if ($[3] !== y) { + t1 = { y }; + $[3] = y; + $[4] = t1; } else { - t2 = $[4]; + t1 = $[4]; } - t0 = t2; + t0 = t1; return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md index 39f301432e..9d232d8e78 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-scope-missing-mutable-range.expect.md @@ -36,17 +36,15 @@ function HomeDiscoStoreItemTileRating(props) { } else { count = $[1]; } - - const t0 = count; - let t1; - if ($[2] !== t0) { - t1 = {t0}; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== count) { + t0 = {count}; + $[2] = count; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md index 23cc7ee846..ceaa350012 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-on-context-variable.expect.md @@ -67,17 +67,15 @@ function Component(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = [t0]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md new file mode 100644 index 0000000000..d72f34b4fd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.expect.md @@ -0,0 +1,130 @@ + +## Input + +```javascript +import {throwErrorWithMessage, ValidateMemoization} from 'shared-runtime'; + +/** + * Context variables are local variables that (1) have at least one reassignment + * and (2) are captured into a function expression. These have a known mutable + * range: from first declaration / assignment to the last direct or aliased, + * mutable reference. + * + * This fixture validates that forget can take granular dependencies on context + * variables when the reference to a context var happens *after* the end of its + * mutable range. + */ +function Component({cond, a}) { + let contextVar; + if (cond) { + contextVar = {val: a}; + } else { + contextVar = {}; + throwErrorWithMessage(''); + } + const cb = {cb: () => contextVar.val * 4}; + + /** + * manually specify input to avoid adding a `PropertyLoad` from contextVar, + * which might affect hoistable-objects analysis. + */ + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: false, a: undefined}], + sequentialRenders: [ + {cond: true, a: 2}, + {cond: true, a: 2}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { throwErrorWithMessage, ValidateMemoization } from "shared-runtime"; + +/** + * Context variables are local variables that (1) have at least one reassignment + * and (2) are captured into a function expression. These have a known mutable + * range: from first declaration / assignment to the last direct or aliased, + * mutable reference. + * + * This fixture validates that forget can take granular dependencies on context + * variables when the reference to a context var happens *after* the end of its + * mutable range. + */ +function Component(t0) { + const $ = _c(10); + const { cond, a } = t0; + let contextVar; + if ($[0] !== a || $[1] !== cond) { + if (cond) { + contextVar = { val: a }; + } else { + contextVar = {}; + throwErrorWithMessage(""); + } + $[0] = a; + $[1] = cond; + $[2] = contextVar; + } else { + contextVar = $[2]; + } + let t1; + if ($[3] !== contextVar.val) { + t1 = { cb: () => contextVar.val * 4 }; + $[3] = contextVar.val; + $[4] = t1; + } else { + t1 = $[4]; + } + const cb = t1; + + const t2 = cond ? a : undefined; + let t3; + if ($[5] !== t2) { + t3 = [t2]; + $[5] = t2; + $[6] = t3; + } else { + t3 = $[6]; + } + let t4; + if ($[7] !== cb || $[8] !== t3) { + t4 = ( + + ); + $[7] = cb; + $[8] = t3; + $[9] = t4; + } else { + t4 = $[9]; + } + return t4; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ cond: false, a: undefined }], + sequentialRenders: [ + { cond: true, a: 2 }, + { cond: true, a: 2 }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[2],"output":{"cb":"[[ function params=0 ]]"}}
+
{"inputs":[2],"output":{"cb":"[[ function params=0 ]]"}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js new file mode 100644 index 0000000000..b9bdd67e2f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/context-var-granular-dep.js @@ -0,0 +1,43 @@ +import {throwErrorWithMessage, ValidateMemoization} from 'shared-runtime'; + +/** + * Context variables are local variables that (1) have at least one reassignment + * and (2) are captured into a function expression. These have a known mutable + * range: from first declaration / assignment to the last direct or aliased, + * mutable reference. + * + * This fixture validates that forget can take granular dependencies on context + * variables when the reference to a context var happens *after* the end of its + * mutable range. + */ +function Component({cond, a}) { + let contextVar; + if (cond) { + contextVar = {val: a}; + } else { + contextVar = {}; + throwErrorWithMessage(''); + } + const cb = {cb: () => contextVar.val * 4}; + + /** + * manually specify input to avoid adding a `PropertyLoad` from contextVar, + * which might affect hoistable-objects analysis. + */ + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{cond: false, a: undefined}], + sequentialRenders: [ + {cond: true, a: 2}, + {cond: true, a: 2}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md index d8e59c486a..b7c425ba5c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-scope-missing-mutable-range.expect.md @@ -35,17 +35,15 @@ function HomeDiscoStoreItemTileRating(props) { } else { count = $[1]; } - - const t0 = count; - let t1; - if ($[2] !== t0) { - t1 = {t0}; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== count) { + t0 = {count}; + $[2] = count; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } - return t1; + return t0; } ``` 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 d94a5e7e37..e335273026 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 @@ -88,36 +88,34 @@ function Inner(props) { input; input; let t0; - const t1 = input; - let t2; - if ($[0] !== t1) { - t2 = [t1]; - $[0] = t1; - $[1] = t2; + let t1; + if ($[0] !== input) { + t1 = [input]; + $[0] = input; + $[1] = t1; } else { - t2 = $[1]; + t1 = $[1]; } - t0 = t2; + t0 = t1; const output = t0; - const t3 = input; - let t4; - if ($[2] !== t3) { - t4 = [t3]; - $[2] = t3; - $[3] = t4; + let t2; + if ($[2] !== input) { + t2 = [input]; + $[2] = input; + $[3] = t2; } else { - t4 = $[3]; + t2 = $[3]; } - let t5; - if ($[4] !== output || $[5] !== t4) { - t5 = ; + let t3; + if ($[4] !== output || $[5] !== t2) { + t3 = ; $[4] = output; - $[5] = t4; - $[6] = t5; + $[5] = t2; + $[6] = t3; } else { - t5 = $[6]; + t3 = $[6]; } - return t5; + return t3; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/snap/src/sprout/index.ts b/compiler/packages/snap/src/sprout/index.ts index 733be561c0..04748bed28 100644 --- a/compiler/packages/snap/src/sprout/index.ts +++ b/compiler/packages/snap/src/sprout/index.ts @@ -32,7 +32,15 @@ export function runSprout( originalCode: string, forgetCode: string, ): SproutResult { - const forgetResult = doEval(forgetCode); + let forgetResult; + try { + (globalThis as any).__SNAP_EVALUATOR_MODE = 'forget'; + forgetResult = doEval(forgetCode); + } catch (e) { + throw e; + } finally { + (globalThis as any).__SNAP_EVALUATOR_MODE = undefined; + } if (forgetResult.kind === 'UnexpectedError') { return makeError('Unexpected error in Forget runner', forgetResult.value); } diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 58815842cb..1b8648f4ff 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -259,26 +259,35 @@ export function Throw() { export function ValidateMemoization({ inputs, - output, + output: rawOutput, + onlyCheckCompiled = false, }: { inputs: Array; output: any; + onlyCheckCompiled: boolean; }): React.ReactElement { 'use no forget'; + // Wrap rawOutput as it might be a function, which useState would invoke. + const output = {value: rawOutput}; const [previousInputs, setPreviousInputs] = React.useState(inputs); const [previousOutput, setPreviousOutput] = React.useState(output); if ( - inputs.length !== previousInputs.length || - inputs.some((item, i) => item !== previousInputs[i]) + onlyCheckCompiled && + (globalThis as any).__SNAP_EVALUATOR_MODE === 'forget' ) { - // Some input changed, we expect the output to change - setPreviousInputs(inputs); - setPreviousOutput(output); - } else if (output !== previousOutput) { - // Else output should be stable - throw new Error('Output identity changed but inputs did not'); + if ( + inputs.length !== previousInputs.length || + inputs.some((item, i) => item !== previousInputs[i]) + ) { + // Some input changed, we expect the output to change + setPreviousInputs(inputs); + setPreviousOutput(output); + } else if (output.value !== previousOutput.value) { + // Else output should be stable + throw new Error('Output identity changed but inputs did not'); + } } - return React.createElement(Stringify, {inputs, output}); + return React.createElement(Stringify, {inputs, output: rawOutput}); } export function createHookWrapper( From 00546339b77a9a908f12dee645473b0593d573ac Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 15:18:13 -0500 Subject: [PATCH 136/422] [compiler][ez] Add shape for global Object.keys Add shape / type for global Object.keys. This is useful because - it has an Effect.Read (not an Effect.Capture) as it cannot alias its argument. - Object.keys return an array --- .../src/HIR/Globals.ts | 15 ++++++ .../compiler/shapes-object-key.expect.md | 49 +++++++++++++++++++ .../fixtures/compiler/shapes-object-key.ts | 11 +++++ 3 files changed, 75 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts index 2525b87bd8..c85de65f06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -87,6 +87,21 @@ const UNTYPED_GLOBALS: Set = new Set([ ]); const TYPED_GLOBALS: Array<[string, BuiltInType]> = [ + [ + 'Object', + addObject(DEFAULT_SHAPES, 'Object', [ + [ + 'keys', + addFunction(DEFAULT_SHAPES, [], { + positionalParams: [Effect.Read], + restParam: null, + returnType: {kind: 'Object', shapeId: BuiltInArrayId}, + calleeEffect: Effect.Read, + returnValueKind: ValueKind.Mutable, + }), + ], + ]), + ], [ 'Array', addObject(DEFAULT_SHAPES, 'Array', [ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md new file mode 100644 index 0000000000..e491eb6c69 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.expect.md @@ -0,0 +1,49 @@ + +## Input + +```javascript +import {arrayPush} from 'shared-runtime'; + +function useFoo({a, b}) { + const obj = {a}; + arrayPush(Object.keys(obj), b); + return obj; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2, b: 3}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { arrayPush } from "shared-runtime"; + +function useFoo(t0) { + const $ = _c(2); + const { a, b } = t0; + let t1; + if ($[0] !== a) { + t1 = { a }; + $[0] = a; + $[1] = t1; + } else { + t1 = $[1]; + } + const obj = t1; + arrayPush(Object.keys(obj), b); + return obj; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: 2, b: 3 }], +}; + +``` + +### Eval output +(kind: ok) {"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts new file mode 100644 index 0000000000..9dbaac79c6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/shapes-object-key.ts @@ -0,0 +1,11 @@ +import {arrayPush} from 'shared-runtime'; + +function useFoo({a, b}) { + const obj = {a}; + arrayPush(Object.keys(obj), b); + return obj; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2, b: 3}], +}; From 2ccca4808b4540926565eb152b940ffcabd6f50e Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 16 Dec 2024 15:31:21 -0500 Subject: [PATCH 137/422] [compiler][be] Clean up bug + feature test fixtures Test fixtures testing different compiler features (e.g. non-auto memoization) should live in separate directories. Remove bug-prefixed fixtures that have since been fixed --- ...hoisting-functionexpr-conditional-dep.expect.md} | 4 +++- ...sx => hoisting-functionexpr-conditional-dep.tsx} | 0 .../infer-deps-custom-config.expect.md | 0 .../infer-deps-custom-config.js | 0 .../infer-effect-dependencies.expect.md | 0 .../infer-effect-dependencies.js | 0 .../{ => inferEffectDeps}/nonreactive-dep.expect.md | 0 .../{ => inferEffectDeps}/nonreactive-dep.js | 0 .../nonreactive-ref-helper.expect.md | 0 .../{ => inferEffectDeps}/nonreactive-ref-helper.js | 0 .../{ => inferEffectDeps}/nonreactive-ref.expect.md | 0 .../{ => inferEffectDeps}/nonreactive-ref.js | 0 .../outlined-function.expect.md | 0 .../{ => inferEffectDeps}/outlined-function.js | 0 .../pruned-nonreactive-obj.expect.md | 0 .../{ => inferEffectDeps}/pruned-nonreactive-obj.js | 0 .../reactive-memberexpr-merge.expect.md | 0 .../reactive-memberexpr-merge.js | 0 .../reactive-memberexpr.expect.md | 0 .../{ => inferEffectDeps}/reactive-memberexpr.js | 0 .../reactive-optional-chain.expect.md | 0 .../reactive-optional-chain.js | 0 .../reactive-variable.expect.md | 0 .../{ => inferEffectDeps}/reactive-variable.js | 0 .../todo-import-namespace-useEffect.expect.md | 0 .../todo-import-namespace-useEffect.js | 0 ...-merge-uncond-optional-chain-and-cond.expect.md} | 5 ++++- ...ge-case-merge-uncond-optional-chain-and-cond.ts} | 0 ...nfer-function-cond-access-not-hoisted.expect.md} | 6 +++++- ...x => infer-function-cond-access-not-hoisted.tsx} | 0 ...md => try-catch-maybe-null-dependency.expect.md} | 7 ++++++- ...ndency.ts => try-catch-maybe-null-dependency.ts} | 0 compiler/packages/snap/src/SproutTodoFilter.ts | 13 +++---------- 33 files changed, 21 insertions(+), 14 deletions(-) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{bug-invalid-hoisting-functionexpr.expect.md => hoisting-functionexpr-conditional-dep.expect.md} (95%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{bug-invalid-hoisting-functionexpr.tsx => hoisting-functionexpr-conditional-dep.tsx} (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-deps-custom-config.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-deps-custom-config.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-effect-dependencies.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/infer-effect-dependencies.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-dep.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-dep.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref-helper.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref-helper.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/nonreactive-ref.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/outlined-function.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/outlined-function.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/pruned-nonreactive-obj.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/pruned-nonreactive-obj.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr-merge.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr-merge.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-memberexpr.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-optional-chain.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-optional-chain.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-variable.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/reactive-variable.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/todo-import-namespace-useEffect.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{ => inferEffectDeps}/todo-import-namespace-useEffect.js (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/{bug-merge-uncond-optional-chain-and-cond.expect.md => edge-case-merge-uncond-optional-chain-and-cond.expect.md} (94%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/{bug-merge-uncond-optional-chain-and-cond.ts => edge-case-merge-uncond-optional-chain-and-cond.ts} (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/{bug-infer-function-cond-access-not-hoisted.expect.md => infer-function-cond-access-not-hoisted.expect.md} (82%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/{bug-infer-function-cond-access-not-hoisted.tsx => infer-function-cond-access-not-hoisted.tsx} (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{bug-try-catch-maybe-null-dependency.expect.md => try-catch-maybe-null-dependency.expect.md} (95%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{bug-try-catch-maybe-null-dependency.ts => try-catch-maybe-null-dependency.ts} (100%) 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/hoisting-functionexpr-conditional-dep.expect.md similarity index 95% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-functionexpr-conditional-dep.expect.md index d6331db4e7..e99e9e1c80 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/hoisting-functionexpr-conditional-dep.expect.md @@ -90,4 +90,6 @@ export const FIXTURE_ENTRYPOINT = { }; ``` - \ No newline at end of file + +### Eval output +(kind: ok)
{"shouldInvokeFns":true,"callback":{"kind":"Function","result":null}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-functionexpr-conditional-dep.tsx similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.tsx rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-functionexpr-conditional-dep.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-deps-custom-config.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/infer-effect-dependencies.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-dep.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref-helper.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/nonreactive-ref.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/outlined-function.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/pruned-nonreactive-obj.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr-merge.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-memberexpr.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-optional-chain.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/reactive-variable.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inferEffectDeps/todo-import-namespace-useEffect.js 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/edge-case-merge-uncond-optional-chain-and-cond.expect.md similarity index 94% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/edge-case-merge-uncond-optional-chain-and-cond.expect.md index fa265ae1f8..d9361bee32 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/edge-case-merge-uncond-optional-chain-and-cond.expect.md @@ -85,4 +85,7 @@ export const FIXTURE_ENTRYPOINT = { }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) {} +[[ (exception in render) TypeError: Cannot read properties of null (reading 'title_text') ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/edge-case-merge-uncond-optional-chain-and-cond.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/edge-case-merge-uncond-optional-chain-and-cond.ts 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/infer-function-cond-access-not-hoisted.expect.md similarity index 82% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md index f68c826507..263dd4d2e7 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/infer-function-cond-access-not-hoisted.expect.md @@ -70,4 +70,8 @@ export const FIXTURE_ENTRYPOINT = { }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]] +
{"fn":{"kind":"Function","result":null},"shouldInvokeFns":true}
+
{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-cond-access-not-hoisted.tsx similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.tsx rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-cond-access-not-hoisted.tsx 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/try-catch-maybe-null-dependency.expect.md similarity index 95% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-maybe-null-dependency.expect.md index 839821b349..52b359839f 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/try-catch-maybe-null-dependency.expect.md @@ -70,4 +70,9 @@ export const FIXTURE_ENTRYPOINT = { }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) ["null"] +[null] +[null] +["null"] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-maybe-null-dependency.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-maybe-null-dependency.ts diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 3610707396..981aa1d918 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -477,22 +477,15 @@ const skipFilter = new Set([ 'invalid-jsx-lowercase-localvar', // bugs - 'fbt/bug-fbt-plural-multiple-function-calls', - 'fbt/bug-fbt-plural-multiple-mixed-call-tag', - `bug-capturing-func-maybealias-captured-mutate`, 'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr', - 'bug-invalid-hoisting-functionexpr', + `bug-capturing-func-maybealias-captured-mutate`, 'bug-aliased-capture-aliased-mutate', 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', - 'bug-try-catch-maybe-null-dependency', 'bug-type-inference-control-flow', - 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', + 'fbt/bug-fbt-plural-multiple-function-calls', + 'fbt/bug-fbt-plural-multiple-mixed-call-tag', 'bug-invalid-phi-as-dependency', - 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond', - 'original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block', - 'original-reactive-scopes-fork/bug-hoisted-declaration-with-scope', - 'bug-codegen-inline-iife', // 'react-compiler-runtime' not yet supported 'flag-enable-emit-hook-guards', From 9c1760b917fea9e4f8c66edccfedaad380cb5976 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 26 Dec 2024 20:03:48 -0500 Subject: [PATCH 138/422] [compiler] rewrite invariant in InferReferenceEffects Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: --- .../src/Inference/InferFunctionEffects.ts | 6 +- .../src/Inference/InferReferenceEffects.ts | 75 +++++++++++-------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts index 0ae54839b6..8425ff79ed 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts @@ -41,11 +41,15 @@ function inferOperandEffect(state: State, place: Place): null | FunctionEffect { if (isRefOrRefValue(place.identifier)) { break; } else if (value.kind === ValueKind.Context) { + CompilerError.invariant(value.context.size > 0, { + reason: 'Expected context value places', + loc: place.loc, + }); return { kind: 'ContextMutation', loc: place.loc, effect: place.effect, - places: value.context.size === 0 ? new Set([place]) : value.context, + places: value.context, }; } else if ( value.kind !== ValueKind.Mutable && diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 8cf30a9666..0d12f373a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -16,6 +16,7 @@ import { FunctionEffect, GeneratedSource, HIRFunction, + Identifier, IdentifierId, InstructionKind, InstructionValue, @@ -857,17 +858,19 @@ function inferBlock( break; } case 'ArrayExpression': { - const valueKind: AbstractValue = hasContextRefOperand(state, instrValue) - ? { - kind: ValueKind.Context, - reason: new Set([ValueReason.Other]), - context: new Set(), - } - : { - kind: ValueKind.Mutable, - reason: new Set([ValueReason.Other]), - context: new Set(), - }; + const contextRefOperands = getContextRefOperand(state, instrValue); + const valueKind: AbstractValue = + contextRefOperands.length > 0 + ? { + kind: ValueKind.Context, + reason: new Set([ValueReason.Other]), + context: new Set(contextRefOperands), + } + : { + kind: ValueKind.Mutable, + reason: new Set([ValueReason.Other]), + context: new Set(), + }; continuation = { kind: 'initialize', valueKind, @@ -918,17 +921,19 @@ function inferBlock( break; } case 'ObjectExpression': { - const valueKind: AbstractValue = hasContextRefOperand(state, instrValue) - ? { - kind: ValueKind.Context, - reason: new Set([ValueReason.Other]), - context: new Set(), - } - : { - kind: ValueKind.Mutable, - reason: new Set([ValueReason.Other]), - context: new Set(), - }; + const contextRefOperands = getContextRefOperand(state, instrValue); + const valueKind: AbstractValue = + contextRefOperands.length > 0 + ? { + kind: ValueKind.Context, + reason: new Set([ValueReason.Other]), + context: new Set(contextRefOperands), + } + : { + kind: ValueKind.Mutable, + reason: new Set([ValueReason.Other]), + context: new Set(), + }; for (const property of instrValue.properties) { switch (property.kind) { @@ -1593,15 +1598,20 @@ function inferBlock( } case 'LoadLocal': { const lvalue = instr.lvalue; - const effect = - state.isDefined(lvalue) && - state.kind(lvalue).kind === ValueKind.Context - ? Effect.ConditionallyMutate - : Effect.Capture; + CompilerError.invariant( + !( + state.isDefined(lvalue) && + state.kind(lvalue).kind === ValueKind.Context + ), + { + reason: 'Unexpected LoadLocal with context lvalue', + loc: lvalue.loc, + }, + ); state.referenceAndRecordEffects( freezeActions, instrValue.place, - effect, + Effect.Capture, ValueReason.Other, ); lvalue.effect = Effect.ConditionallyMutate; @@ -1932,19 +1942,20 @@ function inferBlock( ); } -function hasContextRefOperand( +function getContextRefOperand( state: InferenceState, instrValue: InstructionValue, -): boolean { +): Array { + const result = []; for (const place of eachInstructionValueOperand(instrValue)) { if ( state.isDefined(place) && state.kind(place).kind === ValueKind.Context ) { - return true; + result.push(place); } } - return false; + return result; } export function getFunctionCallSignature( From 59006b003e9de3577a1e93e89a639e5091b9b92f Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 26 Dec 2024 20:03:48 -0500 Subject: [PATCH 139/422] [compiler] Repro for invalid Array.map type Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: --- ...-invalid-mixedreadonly-map-shape.expect.md | 163 ++++++++++++++++++ .../bug-invalid-mixedreadonly-map-shape.js | 56 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 220 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.expect.md new file mode 100644 index 0000000000..1418062c33 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.expect.md @@ -0,0 +1,163 @@ + +## Input + +```javascript +import { + arrayPush, + identity, + makeArray, + Stringify, + useFragment, +} from 'shared-runtime'; + +/** + * Bug repro showing why it's invalid for function references to be annotated + * with a `Read` effect when that reference might lead to the function being + * invoked. + * + * Note that currently, `Array.map` is annotated to have `Read` effects on its + * operands. This is incorrect as function effects must be replayed when `map` + * is called + * - Read: non-aliasing data dependency + * - Capture: maybe-aliasing data dependency + * - ConditionallyMutate: maybe-aliasing data dependency; maybe-write / invoke + * but only if the value is mutable + * + * Invalid evaluator result: Found differences in evaluator results Non-forget + * (expected): (kind: ok) + *
{"x":[2,2,2],"count":3}
{"item":1}
+ *
{"x":[2,2,2],"count":4}
{"item":1}
+ * Forget: + * (kind: ok) + *
{"x":[2,2,2],"count":3}
{"item":1}
+ *
{"x":[2,2,2,2,2,2],"count":4}
{"item":1}
+ */ + +function Component({extraJsx}) { + const x = makeArray(); + const items = useFragment(); + const jsx = items.a.map((item, i) => { + arrayPush(x, 2); + return ; + }); + const offset = jsx.length; + for (let i = 0; i < extraJsx; i++) { + jsx.push(); + } + const count = jsx.length; + identity(count); + return ( + <> + + {jsx[0]} + + ); +} +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{extraJsx: 0}], + sequentialRenders: [{extraJsx: 0}, {extraJsx: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { + arrayPush, + identity, + makeArray, + Stringify, + useFragment, +} from "shared-runtime"; + +/** + * Bug repro showing why it's invalid for function references to be annotated + * with a `Read` effect when that reference might lead to the function being + * invoked. + * + * Note that currently, `Array.map` is annotated to have `Read` effects on its + * operands. This is incorrect as function effects must be replayed when `map` + * is called + * - Read: non-aliasing data dependency + * - Capture: maybe-aliasing data dependency + * - ConditionallyMutate: maybe-aliasing data dependency; maybe-write / invoke + * but only if the value is mutable + * + * Invalid evaluator result: Found differences in evaluator results Non-forget + * (expected): (kind: ok) + *
{"x":[2,2,2],"count":3}
{"item":1}
+ *
{"x":[2,2,2],"count":4}
{"item":1}
+ * Forget: + * (kind: ok) + *
{"x":[2,2,2],"count":3}
{"item":1}
+ *
{"x":[2,2,2,2,2,2],"count":4}
{"item":1}
+ */ + +function Component(t0) { + const $ = _c(9); + const { extraJsx } = t0; + let t1; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t1 = makeArray(); + $[0] = t1; + } else { + t1 = $[0]; + } + const x = t1; + const items = useFragment(); + let jsx; + if ($[1] !== extraJsx || $[2] !== items.a) { + jsx = items.a.map((item, i) => { + arrayPush(x, 2); + return ; + }); + const offset = jsx.length; + for (let i_0 = 0; i_0 < extraJsx; i_0++) { + jsx.push(); + } + $[1] = extraJsx; + $[2] = items.a; + $[3] = jsx; + } else { + jsx = $[3]; + } + + const count = jsx.length; + identity(count); + let t2; + if ($[4] !== count) { + t2 = ; + $[4] = count; + $[5] = t2; + } else { + t2 = $[5]; + } + const t3 = jsx[0]; + let t4; + if ($[6] !== t2 || $[7] !== t3) { + t4 = ( + <> + {t2} + {t3} + + ); + $[6] = t2; + $[7] = t3; + $[8] = t4; + } else { + t4 = $[8]; + } + return t4; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ extraJsx: 0 }], + sequentialRenders: [{ extraJsx: 0 }, { extraJsx: 1 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.js new file mode 100644 index 0000000000..d038cf6cd3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.js @@ -0,0 +1,56 @@ +import { + arrayPush, + identity, + makeArray, + Stringify, + useFragment, +} from 'shared-runtime'; + +/** + * Bug repro showing why it's invalid for function references to be annotated + * with a `Read` effect when that reference might lead to the function being + * invoked. + * + * Note that currently, `Array.map` is annotated to have `Read` effects on its + * operands. This is incorrect as function effects must be replayed when `map` + * is called + * - Read: non-aliasing data dependency + * - Capture: maybe-aliasing data dependency + * - ConditionallyMutate: maybe-aliasing data dependency; maybe-write / invoke + * but only if the value is mutable + * + * Invalid evaluator result: Found differences in evaluator results Non-forget + * (expected): (kind: ok) + *
{"x":[2,2,2],"count":3}
{"item":1}
+ *
{"x":[2,2,2],"count":4}
{"item":1}
+ * Forget: + * (kind: ok) + *
{"x":[2,2,2],"count":3}
{"item":1}
+ *
{"x":[2,2,2,2,2,2],"count":4}
{"item":1}
+ */ + +function Component({extraJsx}) { + const x = makeArray(); + const items = useFragment(); + const jsx = items.a.map((item, i) => { + arrayPush(x, 2); + return ; + }); + const offset = jsx.length; + for (let i = 0; i < extraJsx; i++) { + jsx.push(); + } + const count = jsx.length; + identity(count); + return ( + <> + + {jsx[0]} + + ); +} +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{extraJsx: 0}], + sequentialRenders: [{extraJsx: 0}, {extraJsx: 1}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 3610707396..f363fd922e 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -486,6 +486,7 @@ const skipFilter = new Set([ 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', + 'bug-invalid-mixedreadonly-map-shape', 'bug-type-inference-control-flow', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', From ff889d9c51ea3e5b1a9ae6c7fc95522ec4e91896 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 26 Dec 2024 20:03:48 -0500 Subject: [PATCH 140/422] [compiler] Fix invalid Array.map type Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: --- .../src/HIR/ObjectShape.ts | 10 ++- ...d => mixedreadonly-mutating-map.expect.md} | 75 +++++++++---------- ...shape.js => mixedreadonly-mutating-map.js} | 3 + .../packages/snap/src/SproutTodoFilter.ts | 1 - 4 files changed, 46 insertions(+), 43 deletions(-) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{bug-invalid-mixedreadonly-map-shape.expect.md => mixedreadonly-mutating-map.expect.md} (78%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{bug-invalid-mixedreadonly-map-shape.js => mixedreadonly-mutating-map.js} (92%) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts index 4482d17890..337f0648dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts @@ -545,8 +545,16 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [ [ 'map', addFunction(BUILTIN_SHAPES, [], { + /** + * Note `map`'s arguments are annotated as Effect.ConditionallyMutate as + * calling `.map(fn)` might invoke `fn`, which means replaying its + * effects. + * + * (Note that -- Effect.Read / Effect.Capture on a function type means + * potential data dependency or aliasing respectively.) + */ positionalParams: [], - restParam: Effect.Read, + restParam: Effect.ConditionallyMutate, returnType: {kind: 'Object', shapeId: BuiltInArrayId}, calleeEffect: Effect.ConditionallyMutate, returnValueKind: ValueKind.Mutable, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mixedreadonly-mutating-map.expect.md similarity index 78% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mixedreadonly-mutating-map.expect.md index 1418062c33..4867388a86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mixedreadonly-mutating-map.expect.md @@ -36,6 +36,9 @@ import { function Component({extraJsx}) { const x = makeArray(); const items = useFragment(); + // This closure has the following effects that must be replayed: + // - MaybeFreeze / Capture of `items` + // - ConditionalMutate of x const jsx = items.a.map((item, i) => { arrayPush(x, 2); return ; @@ -97,60 +100,47 @@ import { */ function Component(t0) { - const $ = _c(9); + const $ = _c(6); const { extraJsx } = t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = makeArray(); - $[0] = t1; - } else { - t1 = $[0]; - } - const x = t1; + const x = makeArray(); const items = useFragment(); - let jsx; - if ($[1] !== extraJsx || $[2] !== items.a) { - jsx = items.a.map((item, i) => { - arrayPush(x, 2); - return ; - }); - const offset = jsx.length; - for (let i_0 = 0; i_0 < extraJsx; i_0++) { - jsx.push(); - } - $[1] = extraJsx; - $[2] = items.a; - $[3] = jsx; - } else { - jsx = $[3]; + + const jsx = items.a.map((item, i) => { + arrayPush(x, 2); + return ; + }); + const offset = jsx.length; + for (let i_0 = 0; i_0 < extraJsx; i_0++) { + jsx.push(); } const count = jsx.length; identity(count); - let t2; - if ($[4] !== count) { - t2 = ; - $[4] = count; - $[5] = t2; + let t1; + if ($[0] !== count || $[1] !== x) { + t1 = ; + $[0] = count; + $[1] = x; + $[2] = t1; } else { - t2 = $[5]; + t1 = $[2]; } - const t3 = jsx[0]; - let t4; - if ($[6] !== t2 || $[7] !== t3) { - t4 = ( + const t2 = jsx[0]; + let t3; + if ($[3] !== t1 || $[4] !== t2) { + t3 = ( <> + {t1} {t2} - {t3} ); - $[6] = t2; - $[7] = t3; - $[8] = t4; + $[3] = t1; + $[4] = t2; + $[5] = t3; } else { - t4 = $[8]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { @@ -160,4 +150,7 @@ export const FIXTURE_ENTRYPOINT = { }; ``` - \ No newline at end of file + +### Eval output +(kind: ok)
{"x":[2,2,2],"count":3}
{"item":1}
+
{"x":[2,2,2],"count":4}
{"item":1}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mixedreadonly-mutating-map.js similarity index 92% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mixedreadonly-mutating-map.js index d038cf6cd3..858a4ab3dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-mixedreadonly-map-shape.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mixedreadonly-mutating-map.js @@ -32,6 +32,9 @@ import { function Component({extraJsx}) { const x = makeArray(); const items = useFragment(); + // This closure has the following effects that must be replayed: + // - MaybeFreeze / Capture of `items` + // - ConditionalMutate of x const jsx = items.a.map((item, i) => { arrayPush(x, 2); return ; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index f363fd922e..3610707396 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -486,7 +486,6 @@ const skipFilter = new Set([ 'bug-aliased-capture-mutate', 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', - 'bug-invalid-mixedreadonly-map-shape', 'bug-type-inference-control-flow', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', From 15ec9d372034999a717c46732e3cf1ce39024e8a Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 27 Dec 2024 19:01:14 -0500 Subject: [PATCH 141/422] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: --- .../src/HIR/BuildHIR.ts | 154 +----------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +-- .../src/HIR/Environment.ts | 2 - .../src/HIR/HIR.ts | 1 - .../HIR/MergeOverlappingReactiveScopesHIR.ts | 20 +- .../src/HIR/PrintHIR.ts | 7 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 148 +++--------- .../Inference/InferMutableContextVariables.ts | 23 +- .../src/Inference/InferReferenceEffects.ts | 50 ++-- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../InferReactiveScopeVariables.ts | 8 + .../src/SSA/EliminateRedundantPhi.ts | 19 ++ .../src/SSA/EnterSSA.ts | 3 - .../src/Transform/TransformFire.ts | 53 +---- ...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 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- ...setstate-captured-indirectly-jsx.expect.md | 82 +++++++ ...isting-setstate-captured-indirectly-jsx.js | 18 ++ .../compiler/hoisting-setstate.expect.md | 92 ++++++++ .../fixtures/compiler/hoisting-setstate.js | 35 +++ .../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 +-- .../fixtures/compiler/todo.expect.md | 33 +++ .../src/__tests__/fixtures/compiler/todo.js | 9 + .../fixtures/compiler/todo2.expect.md | 107 +++++++++ .../src/__tests__/fixtures/compiler/todo2.ts | 41 ++++ .../fixtures/compiler/todo3.expect.md | 63 +++++ .../src/__tests__/fixtures/compiler/todo3.js | 15 ++ .../fixtures/compiler/todo4.expect.md | 220 ++++++++++++++++++ .../src/__tests__/fixtures/compiler/todo4.ts | 75 ++++++ .../fixtures/compiler/todo5.expect.md | 52 +++++ .../src/__tests__/fixtures/compiler/todo5.ts | 10 + .../use-operator-conditional.expect.md | 1 - compiler/scripts/bundle-meta.sh | 75 ++++++ 56 files changed, 1142 insertions(+), 477 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo2.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo2.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo3.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo3.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo4.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo4.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo5.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo5.ts create mode 100755 compiler/scripts/bundle-meta.sh 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 4d9ce6becc..53dec6f57b 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, @@ -75,7 +74,7 @@ export function lower( parent: NodePath | null = null, ): Result { const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs); - const context: Array = []; + const context: HIRFunction['context'] = []; for (const ref of capturedRefs ?? []) { context.push({ @@ -3377,7 +3376,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 @@ -3391,7 +3390,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3404,7 +3403,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4078,14 +4076,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) { @@ -4100,8 +4090,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4109,10 +4098,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 @@ -4123,33 +4110,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())) { @@ -4165,115 +4130,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); } } @@ -4304,13 +4174,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 f3f426df56..6b8697adcd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -239,8 +239,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 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 a6a9cbcd48..4df6da6c17 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/MergeOverlappingReactiveScopesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts index a3740539b2..0fd0424fa3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts @@ -248,13 +248,21 @@ function visitPlace( id: InstructionId, place: Place, {activeScopes, joined}: TraversalState, + isFnExpr: boolean, ): void { + // Here, behavior differs: + // With LoweredFunction.dependencies, we never infer functions as mutating primitives + // as the layer of indirection (LoadLocal etc) ensures we don't treat this as a write + // We make the same "hack" in InferReactiveScopeVariables /** * If an instruction mutates an outer scope, flatten all scopes from the top * of the stack to the mutated outer scope. */ const placeScope = getPlaceScope(id, place); if (placeScope != null && isMutable({id} as any, place)) { + if (isFnExpr && place.identifier.type.kind === 'Primitive') { + return; + } const placeScopeIdx = activeScopes.indexOf(placeScope); if (placeScopeIdx !== -1 && placeScopeIdx !== activeScopes.length - 1) { joined.union([placeScope, ...activeScopes.slice(placeScopeIdx + 1)]); @@ -275,15 +283,21 @@ function getOverlappingReactiveScopes( for (const instr of block.instructions) { visitInstructionId(instr.id, context, state); for (const place of eachInstructionOperand(instr)) { - visitPlace(instr.id, place, state); + visitPlace( + instr.id, + place, + state, + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod', + ); } for (const place of eachInstructionLValue(instr)) { - visitPlace(instr.id, place, state); + visitPlace(instr.id, place, state, false); } } visitInstructionId(block.terminal.id, context, state); for (const place of eachTerminalOperand(block.terminal)) { - visitPlace(block.terminal.id, place, state); + visitPlace(block.terminal.id, place, state, false); } } 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 a6f6c606e1..a6d8c33886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,11 +538,8 @@ 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)) + .map(dep => `${printPlace(dep)}`) .join(','); const effects = instrValue.loweredFunc.func.effects @@ -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 08856e9143..4cb84870a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -738,9 +738,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 75bd8f6811..a9beec16d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -8,11 +8,11 @@ import {CompilerError} from '../CompilerError'; import { Effect, + GeneratedSource, HIRFunction, Identifier, - IdentifierName, + IdentifierId, LoweredFunction, - Place, isRefOrRefValue, makeInstructionId, } from '../HIR'; @@ -23,78 +23,39 @@ import {inferMutableContextVariables} from './InferMutableContextVariables'; import {inferMutableRanges} from './InferMutableRanges'; import inferReferenceEffects from './InferReferenceEffects'; -type Dependency = { - identifier: Identifier; - path: Array; -}; - // Helper class to track indirections such as LoadLocal and PropertyLoad. export class IdentifierState { - properties: Map = new Map(); + properties: Map = new Map(); resolve(identifier: Identifier): Identifier { - const resolved = this.properties.get(identifier); + const resolved = this.properties.get(identifier.id); if (resolved !== undefined) { - return resolved.identifier; + return resolved; } 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, - path: [], - }; - this.properties.set(lvalue.identifier, resolved); + alias(lvalue: Identifier, value: Identifier): void { + this.properties.set(lvalue.id, this.properties.get(value.id) ?? value); } } export default function analyseFunctions(func: HIRFunction): void { - const state = new IdentifierState(); - for (const [_, block] of func.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { 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. + infer(instr.value.loweredFunc); + + /** + * Reset mutable range for outer inferReferenceEffects */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } - case 'LoadLocal': - case 'LoadContext': { - if (instr.lvalue.identifier.name === null) { - state.declareTemporary(instr.lvalue, instr.value.place); + for (const operand of instr.value.loweredFunc.func.context) { + operand.identifier.mutableRange.start = makeInstructionId(0); + operand.identifier.mutableRange.end = makeInstructionId(0); + operand.identifier.scope = null; } break; } @@ -103,14 +64,13 @@ export default function analyseFunctions(func: HIRFunction): void { } } -function lower(func: HIRFunction): void { +function lower(func: HIRFunction) { analyseFunctions(func); inferReferenceEffects(func, {isFunctionExpression: true}); deadCodeElimination(func); inferMutableRanges(func); rewriteInstructionKindsBasedOnReassignment(func); inferReactiveScopeVariables(func); - inferMutableContextVariables(func); func.env.logger?.debugLogIRs?.({ kind: 'hir', name: 'AnalyseFunction (inner)', @@ -118,32 +78,15 @@ function lower(func: HIRFunction): void { }); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { - const mutations = new Map(); +function infer(loweredFunc: LoweredFunction): void { + const knownMutated = inferMutableContextVariables(loweredFunc.func); for (const operand of loweredFunc.func.context) { - if ( - isMutatedOrReassigned(operand.identifier) && - operand.identifier.name !== null - ) { - mutations.set(operand.identifier.name.value, operand.effect); - } - } - - 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; - } - - if (isRefOrRefValue(dep.identifier)) { + const identifier = operand.identifier; + CompilerError.invariant(operand.effect === Effect.Unknown, { + reason: 'Unexpected unknown effect', + loc: GeneratedSource, + }); + if (isRefOrRefValue(identifier)) { /* * TODO: this is a hack to ensure we treat functions which reference refs * as having a capture and therefore being considered mutable. this ensures @@ -151,43 +94,16 @@ function infer( * could be called, and allows us to help ensure it isn't called during * render */ - dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); - if (effect !== undefined) { - dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; - } + operand.effect = Effect.Capture; + } else if (knownMutated.has(operand)) { + operand.effect = Effect.Mutate; + } else if (isMutatedOrReassigned(identifier)) { + // Note that this also reflects if identifier is ConditionallyMutated + operand.effect = Effect.Capture; + } else { + operand.effect = Effect.Read; } } - - /* - * This could potentially add duplicate deps to mutatedDeps in the case of - * mutating a context ref in the child function and in this parent function. - * It might be useful to dedupe this. - * - * In practice this never really matters because the Component function has no - * context refs, so it will never have duplicate deps. - */ - for (const place of context) { - CompilerError.invariant(place.identifier.name !== null, { - reason: 'context refs should always have a name', - description: null, - loc: place.loc, - suggestions: null, - }); - - const effect = mutations.get(place.identifier.name.value); - if (effect !== undefined) { - place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); - } - } - - for (const operand of loweredFunc.func.context) { - operand.identifier.mutableRange.start = makeInstructionId(0); - operand.identifier.mutableRange.end = makeInstructionId(0); - operand.identifier.scope = null; - } } function isMutatedOrReassigned(id: Identifier): boolean { 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..0025472721 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -55,32 +55,21 @@ import {IdentifierState} from './AnalyseFunctions'; * fn(); * ``` */ -export function inferMutableContextVariables(fn: HIRFunction): void { +export function inferMutableContextVariables(fn: HIRFunction): Set { const state = new IdentifierState(); const knownMutatedIdentifiers = new Set(); 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 'PropertyLoad': 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, ''); + state.alias(instr.lvalue.identifier, instr.value.object.identifier); break; } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { - state.declareTemporary(instr.lvalue, instr.value.place); + state.alias(instr.lvalue.identifier, instr.value.place.identifier); } break; } @@ -95,11 +84,13 @@ export function inferMutableContextVariables(fn: HIRFunction): void { visitOperand(state, knownMutatedIdentifiers, operand); } } + const results = new Set(); for (const operand of fn.context) { if (knownMutatedIdentifiers.has(operand.identifier)) { - operand.effect = Effect.Mutate; + results.add(operand); } } + return results; } function visitOperand( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 0d12f373a0..5501c7dd68 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -16,7 +16,6 @@ import { FunctionEffect, GeneratedSource, HIRFunction, - Identifier, IdentifierId, InstructionKind, InstructionValue, @@ -396,24 +395,29 @@ class InferenceState { reason, context: new Set(), }); - if (value.kind === 'FunctionExpression') { - if ( - this.#env.config.enablePreserveExistingMemoizationGuarantees || - this.#env.config.enableTransitivelyFreezeFunctionExpressions - ) { - if (value.kind === 'FunctionExpression') { - /* - * We want to freeze the captured values, not mark the operands - * themselves as frozen. There could be mutations that occur - * before the freeze we are processing, and it would be invalid - * to overwrite those mutations as a freeze. - */ - for (const operand of eachInstructionValueOperand(value)) { - const operandValues = this.#variables.get(operand.identifier.id); - if (operandValues !== undefined) { - this.freezeValues(operandValues, reason); - } + if ( + value.kind === 'FunctionExpression' && + (this.#env.config.enablePreserveExistingMemoizationGuarantees || + this.#env.config.enableTransitivelyFreezeFunctionExpressions) + ) { + for (const operand of value.loweredFunc.func.context) { + const operandValues = this.#variables.get(operand.identifier.id); + if (operandValues !== undefined) { + if ( + operandValues.size === 1 && + operandValues.values().next().value?.kind === 'DeclareContext' + ) { + /** + * Avoid freezing hoisted context declarations + * function Component() { + * const cb = useBar(() => foo(2)); // produces a hoisted context declaration + * const foo = useFoo(); // reassigns to the context variable + * return ; + * } + */ + continue; } + this.freezeValues(operandValues, reason); } } } @@ -1144,17 +1148,17 @@ function inferBlock( case 'ObjectMethod': case 'FunctionExpression': { let hasMutableOperand = false; - const mutableOperands: Array = []; for (const operand of eachInstructionOperand(instr)) { + CompilerError.invariant(operand.effect !== Effect.Unknown, { + reason: 'Expected fn effects to be populated', + loc: operand.loc, + }); state.referenceAndRecordEffects( freezeActions, operand, - operand.effect === Effect.Unknown ? Effect.Read : operand.effect, + operand.effect, ValueReason.Other, ); - if (isMutableEffect(operand.effect, operand.loc)) { - mutableOperands.push(operand); - } hasMutableOperand ||= isMutableEffect(operand.effect, operand.loc); } /* 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/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts index 1108422f07..1f104d8592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -379,6 +379,14 @@ export function findDisjointMutableValues( */ operand.identifier.mutableRange.start > 0 ) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + if (operand.identifier.type.kind === 'Primitive') { + continue; + } + } operands.push(operand.identifier); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..37394daa8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -13,6 +13,8 @@ import { eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +143,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts index a35c4ddb01..a480b5d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts @@ -319,51 +319,6 @@ function visitFunctionExpressionAndPropagateFireDependencies( replaceFireFunctions(fnExpr.loweredFunc.func, context), ); - /* - * Make a mapping from each dependency to the corresponding LoadLocal for it so that - * we can replace the loaded place with the generated fire function binding - */ - const loadLocalsToDepLoads = new Map(); - for (const dep of fnExpr.loweredFunc.dependencies) { - const loadLocal = context.getLoadLocalInstr(dep.identifier.id); - if (loadLocal != null) { - loadLocalsToDepLoads.set(loadLocal.place.identifier.id, loadLocal); - } - } - - const replacedCallees = new Map(); - for (const [ - calleeIdentifierId, - loadedFireFunctionBindingPlace, - ] of calleesCapturedByFnExpression.entries()) { - /* - * Given the ids of captured fire callees, look at the deps for loads of those identifiers - * and replace them with the new fire function binding - */ - const loadLocal = loadLocalsToDepLoads.get(calleeIdentifierId); - if (loadLocal == null) { - context.pushError({ - loc: fnExpr.loc, - description: null, - severity: ErrorSeverity.Invariant, - reason: - '[InsertFire] No loadLocal found for fire call argument for lambda', - suggestions: null, - }); - continue; - } - - const oldPlaceId = loadLocal.place.identifier.id; - loadLocal.place = { - ...loadedFireFunctionBindingPlace.fireFunctionBinding, - }; - - replacedCallees.set( - oldPlaceId, - loadedFireFunctionBindingPlace.fireFunctionBinding, - ); - } - // For each replaced callee, update the context of the function expression to track it for ( let contextIdx = 0; @@ -371,9 +326,13 @@ function visitFunctionExpressionAndPropagateFireDependencies( contextIdx++ ) { const contextItem = fnExpr.loweredFunc.func.context[contextIdx]; - const replacedCallee = replacedCallees.get(contextItem.identifier.id); + const replacedCallee = calleesCapturedByFnExpression.get( + contextItem.identifier.id, + ); if (replacedCallee != null) { - fnExpr.loweredFunc.func.context[contextIdx] = replacedCallee; + fnExpr.loweredFunc.func.context[contextIdx] = { + ...replacedCallee.fireFunctionBinding, + }; } } 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-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/hoisting-setstate-captured-indirectly-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.expect.md new file mode 100644 index 0000000000..a77d07cf5a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +function useFoo() { + const onClick = response => { + setState(DISABLED_FORM); + }; + + const [state, setState] = useState(); + const handleLogout = useCallback(() => { + setState(DISABLED_FORM); + }, [setState]); + const getComponent = () => { + return handleLogout()} />; + }; + + // this `getComponent` call is now inferred to be mutating + // setState + return [getComponent(), onClick]; // pass onClick to avoid dce +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees +function useFoo() { + const $ = _c(9); + const onClick = (response) => { + setState(DISABLED_FORM); + }; + + const [, t0] = useState(); + const setState = t0; + let t1; + if ($[0] !== setState) { + t1 = () => { + setState(DISABLED_FORM); + }; + $[0] = setState; + $[1] = t1; + } else { + t1 = $[1]; + } + setState; + const handleLogout = t1; + let t2; + if ($[2] !== handleLogout) { + t2 = () => handleLogout()} />; + $[2] = handleLogout; + $[3] = t2; + } else { + t2 = $[3]; + } + const getComponent = t2; + let t3; + if ($[4] !== getComponent) { + t3 = getComponent(); + $[4] = getComponent; + $[5] = t3; + } else { + t3 = $[5]; + } + let t4; + if ($[6] !== onClick || $[7] !== t3) { + t4 = [t3, onClick]; + $[6] = onClick; + $[7] = t3; + $[8] = t4; + } else { + t4 = $[8]; + } + return t4; +} + +``` + +### 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/hoisting-setstate-captured-indirectly-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.js new file mode 100644 index 0000000000..d13bbfe485 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.js @@ -0,0 +1,18 @@ +// @validatePreserveExistingMemoizationGuarantees +function useFoo() { + const onClick = response => { + setState(DISABLED_FORM); + }; + + const [state, setState] = useState(); + const handleLogout = useCallback(() => { + setState(DISABLED_FORM); + }, [setState]); + const getComponent = () => { + return handleLogout()} />; + }; + + // this `getComponent` call is now inferred to be mutating + // setState + return [getComponent(), onClick]; // pass onClick to avoid dce +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.expect.md new file mode 100644 index 0000000000..9758120960 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.expect.md @@ -0,0 +1,92 @@ + +## Input + +```javascript +import {useEffect, useState} from 'react'; +import {Stringify} from 'shared-runtime'; + +function Foo() { + /** + * Previously, this lowered to + * $1 = LoadContext capture setState + * $2 = FunctionExpression deps=$1 context=setState + * [[ at this point, we freeze the `LoadContext setState` instruction, but it will never be referenced again ]] + */ + useEffect(() => setState(2), []); + + /** + * Special case: declare / reassign to hoisted const + * + * What about reassignment to `let`? + * -> makes sense to error with "reassignemnt" message (not mutation message) + */ + const [state, setState] = useState(0); + return ; +} + +/** + * Here is another version +function Component() { + const cb = useBar(() => foo(2)); + const foo = useFoo(); + return ; +} + */ +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], + sequentialRenders: [{}, {}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useEffect, useState } from "react"; +import { Stringify } from "shared-runtime"; + +function Foo() { + const $ = _c(3); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = []; + $[0] = t0; + } else { + t0 = $[0]; + } + useEffect(() => setState(2), t0); + + const [state, t1] = useState(0); + const setState = t1; + let t2; + if ($[1] !== state) { + t2 = ; + $[1] = state; + $[2] = t2; + } else { + t2 = $[2]; + } + return t2; +} + +/** + * Here is another version +function Component() { + const cb = useBar(() => foo(2)); + const foo = useFoo(); + return ; +} + */ +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], + sequentialRenders: [{}, {}], +}; + +``` + +### Eval output +(kind: ok)
{"state":2}
+
{"state":2}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.js new file mode 100644 index 0000000000..77607efb89 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.js @@ -0,0 +1,35 @@ +import {useEffect, useState} from 'react'; +import {Stringify} from 'shared-runtime'; + +function Foo() { + /** + * Previously, this lowered to + * $1 = LoadContext capture setState + * $2 = FunctionExpression deps=$1 context=setState + * [[ at this point, we freeze the `LoadContext setState` instruction, but it will never be referenced again ]] + */ + useEffect(() => setState(2), []); + + /** + * Special case: declare / reassign to hoisted const + * + * What about reassignment to `let`? + * -> makes sense to error with "reassignemnt" message (not mutation message) + */ + const [state, setState] = useState(0); + return ; +} + +/** + * Here is another version +function Component() { + const cb = useBar(() => foo(2)); + const foo = useFoo(); + return ; +} + */ +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], + sequentialRenders: [{}, {}], +}; 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 d34db46d6a..ed0ddda55b 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 e8a3e2d627..3fffec6a7d 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/todo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.expect.md new file mode 100644 index 0000000000..6d9552ecc8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.expect.md @@ -0,0 +1,33 @@ + +## Input + +```javascript +function Component() { + let myObj = getObject(); + useFoo(); + // const cb = () => maybeMutate(myObj ?? []); + const cb = () => (myObj = other()); + foo(cb); + + return myObj; +} + +``` + +## Code + +```javascript +function Component() { + let myObj; + myObj = getObject(); + useFoo(); + + const cb = () => (myObj = other()); + foo(cb); + return myObj; +} + +``` + +### 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/todo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.js new file mode 100644 index 0000000000..c9f56e38a3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.js @@ -0,0 +1,9 @@ +function Component() { + let myObj = getObject(); + useFoo(); + // const cb = () => maybeMutate(myObj ?? []); + const cb = () => (myObj = other()); + foo(cb); + + return myObj; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo2.expect.md new file mode 100644 index 0000000000..c089253684 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo2.expect.md @@ -0,0 +1,107 @@ + +## Input + +```javascript +// @enableTransitivelyFreezeFunctionExpressions:false @enablePropagateDepsInHIR + +function useFoo(reactive) { + const now = localNow(); + useHook(); + + const previousDate = subtractDays(reactive); + + // now aliased to previousDate + const res1 = captureInto(previousDate, now); + const res2 = () => onDateChange(previousDate); + const res3 = mutate(now); // might overwrite previousDate.day + return [res1, res2, res3]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: () => {}, + params: [], +}; + +// function useRLDSPrivateCalendarMeetingPickerHeader( +// disabled: boolean, +// onDateChange: (date: DateTime) => void +// ) { +// const now = DateTime.localNow(); + +// const {date} = useDate(); + +// const previousDate = date.subtractDays(1); +// const nextDate = date.addDays(1); + +// return ( +// onDateChange(previousDate)} +// disabled2={now.getSince(nextDate.getUnixTimestampSeconds())} +// onPress={() => onDateChange(nextDate)} +// /> +// ); +// } + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enableTransitivelyFreezeFunctionExpressions:false @enablePropagateDepsInHIR + +function useFoo(reactive) { + const $ = _c(4); + const now = localNow(); + useHook(); + + const previousDate = subtractDays(reactive); + + const res1 = captureInto(previousDate, now); + const res2 = () => onDateChange(previousDate); + const res3 = mutate(now); + let t0; + if ($[0] !== res1 || $[1] !== res2 || $[2] !== res3) { + t0 = [res1, res2, res3]; + $[0] = res1; + $[1] = res2; + $[2] = res3; + $[3] = t0; + } else { + t0 = $[3]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: () => {}, + params: [], +}; + +// function useRLDSPrivateCalendarMeetingPickerHeader( +// disabled: boolean, +// onDateChange: (date: DateTime) => void +// ) { +// const now = DateTime.localNow(); + +// const {date} = useDate(); + +// const previousDate = date.subtractDays(1); +// const nextDate = date.addDays(1); + +// return ( +// onDateChange(previousDate)} +// disabled2={now.getSince(nextDate.getUnixTimestampSeconds())} +// onPress={() => onDateChange(nextDate)} +// /> +// ); +// } + +``` + +### Eval output +(kind: ok) \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo2.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo2.ts new file mode 100644 index 0000000000..f4ddc44a80 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo2.ts @@ -0,0 +1,41 @@ +// @enableTransitivelyFreezeFunctionExpressions:false @enablePropagateDepsInHIR + +function useFoo(reactive) { + const now = localNow(); + useHook(); + + const previousDate = subtractDays(reactive); + + // now aliased to previousDate + const res1 = captureInto(previousDate, now); + const res2 = () => onDateChange(previousDate); + const res3 = mutate(now); // might overwrite previousDate.day + return [res1, res2, res3]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: () => {}, + params: [], +}; + +// function useRLDSPrivateCalendarMeetingPickerHeader( +// disabled: boolean, +// onDateChange: (date: DateTime) => void +// ) { +// const now = DateTime.localNow(); + +// const {date} = useDate(); + +// const previousDate = date.subtractDays(1); +// const nextDate = date.addDays(1); + +// return ( +// onDateChange(previousDate)} +// disabled2={now.getSince(nextDate.getUnixTimestampSeconds())} +// onPress={() => onDateChange(nextDate)} +// /> +// ); +// } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo3.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo3.expect.md new file mode 100644 index 0000000000..3e1bbe2fe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo3.expect.md @@ -0,0 +1,63 @@ + +## Input + +```javascript +import fbt from 'fbt'; +/** + * See comment in MergeOverlapping (changes MergeOverlapping and InferScope -> + * don't count primitives) + */ +function CometAdsSideFeedUnit({adsSideFeedUnit}) { + let adNodes = adsSideFeedUnit.nodes; + adNodes = adNodes.slice(0, 2); + + const adNodesLength = adNodes.length; + + return adNodes.mmap(i => { + return ; + }); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import fbt from "fbt"; +/** + * See comment in MergeOverlapping (changes MergeOverlapping and InferScope -> + * don't count primitives) + */ +function CometAdsSideFeedUnit(t0) { + const $ = _c(5); + const { adsSideFeedUnit } = t0; + let adNodes = adsSideFeedUnit.nodes; + let t1; + if ($[0] !== adNodes) { + adNodes = adNodes.slice(0, 2); + + const adNodesLength = adNodes.length; + let t2; + if ($[3] !== adNodesLength) { + t2 = (i) => ; + $[3] = adNodesLength; + $[4] = t2; + } else { + t2 = $[4]; + } + t1 = adNodes.mmap(t2); + $[0] = adNodes; + $[1] = t1; + $[2] = adNodes; + } else { + t1 = $[1]; + adNodes = $[2]; + } + return t1; +} + +``` + +### 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/todo3.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo3.js new file mode 100644 index 0000000000..a90de47eef --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo3.js @@ -0,0 +1,15 @@ +import fbt from 'fbt'; +/** + * See comment in MergeOverlapping (changes MergeOverlapping and InferScope -> + * don't count primitives) + */ +function CometAdsSideFeedUnit({adsSideFeedUnit}) { + let adNodes = adsSideFeedUnit.nodes; + adNodes = adNodes.slice(0, 2); + + const adNodesLength = adNodes.length; + + return adNodes.mmap(i => { + return ; + }); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo4.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo4.expect.md new file mode 100644 index 0000000000..b172f6fd8d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo4.expect.md @@ -0,0 +1,220 @@ + +## Input + +```javascript +// @flow +import fbt from 'fbt' +export default component VideoPlayerConfigDebugOverlay( + ..._props: VideoDebugOverlayProps +) { + const [currentFilter, setCurrentFilter] = + useState('all'); + + const [isOzOnly, setIsOzOnly] = useState(true); + + const filters = { + all: { + buttonContent: ( + All + ), + component: VideoPlayerAllConfigValuesDebugItem, + }, + contextSensitive: { + buttonContent: ( + + Context-sensitive values + + ), + component: VideoPlayerContextSensitiveConfigValuesDebugItem, + }, + experiments: { + buttonContent: ( + + Values from experiments + + ), + component: VideoPlayerConfigValuesFromExperimentsDebugItem, + }, + }; + + const CurrentComponent = filters[currentFilter].component; + + return ( +
+ {Object.keys(filters).map(filter => ( + + ))} +
+ +
+
+ name.startsWith(OZ_CONFIG_PREFIX) + : null + } + /> +
+
+ ); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import fbt from "fbt"; +export default function VideoPlayerConfigDebugOverlay(_props) { + const $ = _c(16); + + const [currentFilter, setCurrentFilter] = useState("all"); + + const [isOzOnly, setIsOzOnly] = useState(true); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = { + all: { + buttonContent: fbt._("All", null, { hk: "1Wbb7I" }), + component: VideoPlayerAllConfigValuesDebugItem, + }, + contextSensitive: { + buttonContent: fbt._("Context-sensitive values", null, { + hk: "1KSbMF", + }), + component: VideoPlayerContextSensitiveConfigValuesDebugItem, + }, + experiments: { + buttonContent: fbt._("Values from experiments", null, { hk: "2xcIRT" }), + component: VideoPlayerConfigValuesFromExperimentsDebugItem, + }, + }; + $[0] = t0; + } else { + t0 = $[0]; + } + const filters = t0; + + const CurrentComponent = filters[currentFilter].component; + let t1; + let t2; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = stylex(styles.root); + t2 = Object.keys(filters); + $[1] = t1; + $[2] = t2; + } else { + t1 = $[1]; + t2 = $[2]; + } + let t3; + if ($[3] !== currentFilter) { + t3 = t2.map((filter) => ( + + )); + $[3] = currentFilter; + $[4] = t3; + } else { + t3 = $[4]; + } + let t4; + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { + t4 = stylex(styles.ozFilterContainer); + $[5] = t4; + } else { + t4 = $[5]; + } + let t5; + if ($[6] !== isOzOnly) { + t5 = ( +
+ +
+ ); + $[6] = isOzOnly; + $[7] = t5; + } else { + t5 = $[7]; + } + let t6; + if ($[8] === Symbol.for("react.memo_cache_sentinel")) { + t6 = stylex(styles.content); + $[8] = t6; + } else { + t6 = $[8]; + } + + const t7 = isOzOnly ? _temp : null; + let t8; + if ($[9] !== CurrentComponent || $[10] !== t7) { + t8 = ( +
+ +
+ ); + $[9] = CurrentComponent; + $[10] = t7; + $[11] = t8; + } else { + t8 = $[11]; + } + let t9; + if ($[12] !== t3 || $[13] !== t5 || $[14] !== t8) { + t9 = ( +
+ {t3} + {t5} + {t8} +
+ ); + $[12] = t3; + $[13] = t5; + $[14] = t8; + $[15] = t9; + } else { + t9 = $[15]; + } + return t9; +} +function _temp(name) { + return name.startsWith(OZ_CONFIG_PREFIX); +} + +``` + +### 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/todo4.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo4.ts new file mode 100644 index 0000000000..18def828d8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo4.ts @@ -0,0 +1,75 @@ +// @flow +import fbt from 'fbt' +export default component VideoPlayerConfigDebugOverlay( + ..._props: VideoDebugOverlayProps +) { + const [currentFilter, setCurrentFilter] = + useState('all'); + + const [isOzOnly, setIsOzOnly] = useState(true); + + const filters = { + all: { + buttonContent: ( + All + ), + component: VideoPlayerAllConfigValuesDebugItem, + }, + contextSensitive: { + buttonContent: ( + + Context-sensitive values + + ), + component: VideoPlayerContextSensitiveConfigValuesDebugItem, + }, + experiments: { + buttonContent: ( + + Values from experiments + + ), + component: VideoPlayerConfigValuesFromExperimentsDebugItem, + }, + }; + + const CurrentComponent = filters[currentFilter].component; + + return ( +
+ {Object.keys(filters).map(filter => ( + + ))} +
+ +
+
+ name.startsWith(OZ_CONFIG_PREFIX) + : null + } + /> +
+
+ ); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo5.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo5.expect.md new file mode 100644 index 0000000000..97638fbbcd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo5.expect.md @@ -0,0 +1,52 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback, useRef} from 'react'; + +function Component() { + let object = {}; + const cb = () => object; // maybeFreeze object + object = 2; + useFoo(cb); + return [object, cb]; +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees +import { useCallback, useRef } from "react"; + +function Component() { + const $ = _c(3); + let cb; + let object; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + object = {}; + cb = () => object; + object = 2; + $[0] = cb; + $[1] = object; + } else { + cb = $[0]; + object = $[1]; + } + useFoo(cb); + let t0; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [object, cb]; + $[2] = t0; + } else { + t0 = $[2]; + } + return t0; +} + +``` + +### 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/todo5.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo5.ts new file mode 100644 index 0000000000..bb4e45da4f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo5.ts @@ -0,0 +1,10 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback, useRef} from 'react'; + +function Component() { + let object = {}; + const cb = () => object; // maybeFreeze object + object = 2; + useFoo(cb); + return [object, cb]; +} 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 e335273026..6ad460347f 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; let t1; diff --git a/compiler/scripts/bundle-meta.sh b/compiler/scripts/bundle-meta.sh new file mode 100755 index 0000000000..0fddc1f53e --- /dev/null +++ b/compiler/scripts/bundle-meta.sh @@ -0,0 +1,75 @@ +# !/bin/bash +# (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +# Script to bundle React Forget and prepare for test deployment on Meta devservers. + + +if ! command -v rg >/dev/null; then + echo "\`rg\` not found. You might have to install it (e.g. via Homebrew)." + exit 1 +fi + +set -eo pipefail + +# if [ -z "$COMPILER_PATH" ]; then +# echo "\$COMPILER_PATH must be set to a checkout of \`react-forget\`." +# echo "For example:" +# echo "" +# echo " COMPILER_PATH=path/to/react-forget bundle-meta.sh" +# echo "" +# exit 1 +# fi + +# cd "$COMPILER_PATH"; + +yarn install +rm -rf dist/ +mkdir dist + +packages=("babel-plugin-react-compiler" "eslint-plugin-react-compiler") +for package in "${packages[@]}"; do + echo "Building" "$package" + yarn workspace "$package" run build +done + +echo "Copying artifacts to dist..." +for package in "${packages[@]}"; do + for dir in packages/$package/; do + if [ -d "$dir/dist" ]; then + package_name=$(basename "$dir") + cp -R "$dir/dist" "./dist/$package_name" + fi + done +done + +echo "Hashing dist..." +yarn hash dist > dist/HASH + +if [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then + echo "Writing git commit history..." + git log -n 200 \ + --pretty=format:'{%n "commit": "%H",%n "author": "%aN",%n "date": "%as",%n "title": "%f"%n},' \ + "$@" | \ + perl -pe 'BEGIN{print "["}; END{print "]\n"}' | \ + perl -pe 's/},]/}]/' \ + > dist/commit_history.json +fi + +TMP_FILE="$(mktemp -d)/react-forget.tar.zst" + +# delete tests +find dist -name __tests__ -type d -exec rm -rf {} + +# delete *.d.ts definition files +find dist -name '*.d.ts' -delete +# delete sourcemaps +find dist -name '*.js.map' -delete +# delete typescript compiler cache +find dist -name '*.tsbuildinfo' -delete + +echo "Uploading bundle..." +tar --zstd -cf "$TMP_FILE" dist +HANDLE="$(jf upload "$TMP_FILE" | rg "success File available as (\w+)" | cut -f5 -d' ')" + +echo "Install the bundle in www or fbsource with:" +echo +echo " DEV=1 js1 upgrade react-forget $HANDLE" From fd02db689c70425f1099fd15cb0ca3c741862026 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 27 Dec 2024 19:01:29 -0500 Subject: [PATCH 142/422] [compiler] Remove redundant InferMutableContextVariables Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: --- .../src/Inference/AnalyseFunctions.ts | 9 +- .../Inference/InferMutableContextVariables.ts | 105 ------------------ ...mutate-global-in-effect-fixpoint.expect.md | 68 ++++++------ .../allow-mutate-global-in-effect-fixpoint.js | 7 +- .../reanimated-shared-value-writes.expect.md | 23 ++-- 5 files changed, 55 insertions(+), 157 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts 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 a9beec16d9..ea1f43d6cd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -19,7 +19,6 @@ import { import {deadCodeElimination} from '../Optimization'; import {inferReactiveScopeVariables} from '../ReactiveScopes'; import {rewriteInstructionKindsBasedOnReassignment} from '../SSA'; -import {inferMutableContextVariables} from './InferMutableContextVariables'; import {inferMutableRanges} from './InferMutableRanges'; import inferReferenceEffects from './InferReferenceEffects'; @@ -79,7 +78,6 @@ function lower(func: HIRFunction) { } function infer(loweredFunc: LoweredFunction): void { - const knownMutated = inferMutableContextVariables(loweredFunc.func); for (const operand of loweredFunc.func.context) { const identifier = operand.identifier; CompilerError.invariant(operand.effect === Effect.Unknown, { @@ -95,10 +93,11 @@ function infer(loweredFunc: LoweredFunction): void { * render */ operand.effect = Effect.Capture; - } else if (knownMutated.has(operand)) { - operand.effect = Effect.Mutate; } else if (isMutatedOrReassigned(identifier)) { - // Note that this also reflects if identifier is ConditionallyMutated + /** + * Reflects direct reassignments, PropertyStores, and ConditionallyMutate + * (directly or through maybe-aliases) + */ operand.effect = Effect.Capture; } else { operand.effect = Effect.Read; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts deleted file mode 100644 index 0025472721..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ /dev/null @@ -1,105 +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 {Effect, HIRFunction, Identifier, Place} from '../HIR'; -import { - eachInstructionValueOperand, - eachTerminalOperand, -} from '../HIR/visitors'; -import {IdentifierState} from './AnalyseFunctions'; - -/* - * This pass infers which of the given function's context (free) variables - * are definitively mutated by the function. This analysis is *partial*, - * and only annotates provable mutations, and may miss mutations via indirections. - * The intent of this pass is to drive validations, rejecting known-bad code - * while avoiding false negatives, and the inference should *not* be used to - * drive changes in output. - * - * Note that a complete analysis is possible but would have too many false negatives. - * The approach would be to run LeaveSSA and InferReactiveScopeVariables in order to - * find all possible aliases of a context variable which may be mutated. However, this - * can lead to false negatives: - * - * ``` - * const [x, setX] = useState(null); // x is frozen - * const fn = () => { // context=[x] - * const z = {}; // z is mutable - * foo(z, x); // potentially mutate z and x - * z.a = true; // definitively mutate z - * } - * fn(); - * ``` - * - * When we analyze function expressions we assume that context variables are mutable, - * so we assume that `x` is mutable. We infer that `foo(z, x)` could be mutating the - * two variables to alias each other, such that `z.a = true` could be mutating `x`, - * and we would infer that `x` is definitively mutated. Then when we run - * InferReferenceEffects on the outer code we'd reject it, since there is a definitive - * mutation of a frozen value. - * - * Thus the actual implementation looks at only basic aliasing. The above example would - * pass, since z does not directly alias `x`. However, mutations through trivial aliases - * are detected: - * - * ``` - * const [x, setX] = useState(null); // x is frozen - * const fn = () => { // context=[x] - * const z = x; - * z.a = true; // ERROR: mutates x - * } - * fn(); - * ``` - */ -export function inferMutableContextVariables(fn: HIRFunction): Set { - const state = new IdentifierState(); - const knownMutatedIdentifiers = new Set(); - for (const [, block] of fn.body.blocks) { - for (const instr of block.instructions) { - switch (instr.value.kind) { - case 'PropertyLoad': - case 'ComputedLoad': { - state.alias(instr.lvalue.identifier, instr.value.object.identifier); - break; - } - case 'LoadLocal': - case 'LoadContext': { - if (instr.lvalue.identifier.name === null) { - state.alias(instr.lvalue.identifier, instr.value.place.identifier); - } - break; - } - default: { - for (const operand of eachInstructionValueOperand(instr.value)) { - visitOperand(state, knownMutatedIdentifiers, operand); - } - } - } - } - for (const operand of eachTerminalOperand(block.terminal)) { - visitOperand(state, knownMutatedIdentifiers, operand); - } - } - const results = new Set(); - for (const operand of fn.context) { - if (knownMutatedIdentifiers.has(operand.identifier)) { - results.add(operand); - } - } - return results; -} - -function visitOperand( - state: IdentifierState, - knownMutatedIdentifiers: Set, - operand: Place, -): void { - const resolved = state.resolve(operand.identifier); - if (operand.effect === Effect.Mutate || operand.effect === Effect.Store) { - knownMutatedIdentifiers.add(resolved); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md index 942daec1dd..9b66ac879d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md @@ -19,9 +19,14 @@ function Component() { // capture into a separate variable that is not a context variable. const y = x; + /** + * Note that this fixture currently produces a stale effect closure if `y = x + * = someGlobal` changes between renders. Under current compiler assumptions, + * that would a rule of react violation. + */ useEffect(() => { y.value = 'hello'; - }, []); + }); useEffect(() => { setState(someGlobal.value); @@ -46,57 +51,50 @@ import { useEffect, useState } from "react"; let someGlobal = { value: null }; function Component() { - const $ = _c(7); + const $ = _c(5); const [state, setState] = useState(someGlobal); - let t0; - let t1; - let t2; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - let x = someGlobal; - while (x == null) { - x = someGlobal; - } - const y = x; - t0 = useEffect; - t1 = () => { + let x = someGlobal; + while (x == null) { + x = someGlobal; + } + + const y = x; + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = () => { y.value = "hello"; }; - t2 = []; $[0] = t0; + } else { + t0 = $[0]; + } + useEffect(t0); + let t1; + let t2; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { + setState(someGlobal.value); + }; + t2 = [someGlobal]; $[1] = t1; $[2] = t2; } else { - t0 = $[0]; t1 = $[1]; t2 = $[2]; } - t0(t1, t2); - let t3; + useEffect(t1, t2); + + const t3 = String(state); let t4; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { - t3 = () => { - setState(someGlobal.value); - }; - t4 = [someGlobal]; + if ($[3] !== t3) { + t4 =
{t3}
; $[3] = t3; $[4] = t4; } else { - t3 = $[3]; t4 = $[4]; } - useEffect(t3, t4); - - const t5 = String(state); - let t6; - if ($[5] !== t5) { - t6 =
{t5}
; - $[5] = t5; - $[6] = t6; - } else { - t6 = $[6]; - } - return t6; + return t4; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.js index 84bd42aaef..2f6dfb72df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.js @@ -15,9 +15,14 @@ function Component() { // capture into a separate variable that is not a context variable. const y = x; + /** + * Note that this fixture currently produces a stale effect closure if `y = x + * = someGlobal` changes between renders. Under current compiler assumptions, + * that would a rule of react violation. + */ useEffect(() => { y.value = 'hello'; - }, []); + }); useEffect(() => { setState(someGlobal.value); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reanimated-shared-value-writes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reanimated-shared-value-writes.expect.md index 8f808c94b3..0a19a85428 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reanimated-shared-value-writes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reanimated-shared-value-writes.expect.md @@ -36,21 +36,22 @@ import { useSharedValue } from "react-native-reanimated"; * of render */ function SomeComponent() { - const $ = _c(3); + const $ = _c(2); const sharedVal = useSharedValue(0); - - const T0 = Button; - const t0 = () => (sharedVal.value = Math.random()); - let t1; - if ($[0] !== T0 || $[1] !== t0) { - t1 = ; - $[0] = T0; + let t0; + if ($[0] !== sharedVal) { + t0 = ( + ; +} + +``` + +## Code + +```javascript +// @panicThreshold(none) +"use no memo"; + +function Foo() { + return ; +} +function _temp() { + return alert("hello!"); +} + +``` + +### 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/repro-bailout-nopanic-shouldnt-outline.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.js new file mode 100644 index 0000000000..405295ee4c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.js @@ -0,0 +1,6 @@ +// @panicThreshold(none) +'use no memo'; + +function Foo() { + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md new file mode 100644 index 0000000000..dfc831555f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -0,0 +1,48 @@ + +## Input + +```javascript +// @noEmit + +function Foo() { + 'use memo'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @noEmit + +function Foo() { + "use memo"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} +function _temp() { + return alert("hello!"); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], +}; + +``` + +### Eval output +(kind: ok) \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.js new file mode 100644 index 0000000000..04ec880761 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.js @@ -0,0 +1,11 @@ +// @noEmit + +function Foo() { + 'use memo'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], +}; From 097e7df66835128f470b8dbbfd9299580b4682d2 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 7 May 2025 17:30:28 -0400 Subject: [PATCH 367/422] [compiler][be] Move test pragma to separate file `Environment.ts` is getting complex so let's separate test / playground parsing logic from it --- .../src/HIR/Environment.ts | 197 +---------------- .../src/Utils/TestUtils.ts | 206 ++++++++++++++++++ 2 files changed, 208 insertions(+), 195 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts 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 a487b5086c..6e6643cd1d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -9,15 +9,7 @@ import * as t from '@babel/types'; import {ZodError, z} from 'zod'; import {fromZodError} from 'zod-validation-error'; import {CompilerError} from '../CompilerError'; -import { - CompilationMode, - defaultOptions, - Logger, - PanicThresholdOptions, - parsePluginOptions, - PluginOptions, - ProgramContext, -} from '../Entrypoint'; +import {Logger, ProgramContext} from '../Entrypoint'; import {Err, Ok, Result} from '../Utils/Result'; import { DEFAULT_GLOBALS, @@ -158,7 +150,7 @@ export type Hook = z.infer; * missing some recursive Object / Function shapeIds */ -const EnvironmentConfigSchema = z.object({ +export const EnvironmentConfigSchema = z.object({ customHooks: z.map(z.string(), HookSchema).default(new Map()), /** @@ -640,191 +632,6 @@ const EnvironmentConfigSchema = z.object({ export type EnvironmentConfig = z.infer; -/** - * For test fixtures and playground only. - * - * Pragmas are straightforward to parse for boolean options (`:true` and - * `:false`). These are 'enabled' config values for non-boolean configs (i.e. - * what is used when parsing `:true`). - */ -const testComplexConfigDefaults: PartialEnvironmentConfig = { - validateNoCapitalizedCalls: [], - enableChangeDetectionForDebugging: { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }, - enableEmitFreeze: { - source: 'react-compiler-runtime', - importSpecifierName: 'makeReadOnly', - }, - enableEmitInstrumentForget: { - fn: { - source: 'react-compiler-runtime', - importSpecifierName: 'useRenderCounter', - }, - gating: { - source: 'react-compiler-runtime', - importSpecifierName: 'shouldInstrument', - }, - globalGating: 'DEV', - }, - enableEmitHookGuards: { - source: 'react-compiler-runtime', - importSpecifierName: '$dispatcherGuard', - }, - inlineJsxTransform: { - elementSymbol: 'react.transitional.element', - globalDevVar: 'DEV', - }, - lowerContextAccess: { - source: 'react-compiler-runtime', - importSpecifierName: 'useContext_withSelector', - }, - inferEffectDependencies: [ - { - function: { - source: 'react', - importSpecifierName: 'useEffect', - }, - numRequiredArgs: 1, - }, - { - function: { - source: 'shared-runtime', - importSpecifierName: 'useSpecialEffect', - }, - numRequiredArgs: 2, - }, - { - function: { - source: 'useEffectWrapper', - importSpecifierName: 'default', - }, - numRequiredArgs: 1, - }, - ], -}; - -/** - * For snap test fixtures and playground only. - */ -function parseConfigPragmaEnvironmentForTest( - pragma: string, -): EnvironmentConfig { - const maybeConfig: any = {}; - // Get the defaults to programmatically check for boolean properties - const defaultConfig = EnvironmentConfigSchema.parse({}); - - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - const keyVal = token.slice(1); - let [key, val = undefined] = keyVal.split(':'); - const isSet = val === undefined || val === 'true'; - - if (isSet && key in testComplexConfigDefaults) { - maybeConfig[key] = - testComplexConfigDefaults[key as keyof PartialEnvironmentConfig]; - continue; - } - - if (key === 'customMacros' && val) { - const valSplit = val.split('.'); - if (valSplit.length > 0) { - const props = []; - for (const elt of valSplit.slice(1)) { - if (elt === '*') { - props.push({type: 'wildcard'}); - } else if (elt.length > 0) { - props.push({type: 'name', name: elt}); - } - } - maybeConfig[key] = [[valSplit[0], props]]; - } - continue; - } - - if ( - key !== 'enableResetCacheOnSourceFileChanges' && - typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean' - ) { - // skip parsing non-boolean properties - continue; - } - if (val === undefined || val === 'true') { - maybeConfig[key] = true; - } else { - maybeConfig[key] = false; - } - } - const config = EnvironmentConfigSchema.safeParse(maybeConfig); - if (config.success) { - /** - * Unless explicitly enabled, do not insert HMR handling code - * in test fixtures or playground to reduce visual noise. - */ - if (config.data.enableResetCacheOnSourceFileChanges == null) { - config.data.enableResetCacheOnSourceFileChanges = false; - } - return config.data; - } - CompilerError.invariant(false, { - reason: 'Internal error, could not parse config from pragma string', - description: `${fromZodError(config.error)}`, - loc: null, - suggestions: null, - }); -} -export function parseConfigPragmaForTests( - pragma: string, - defaults: { - compilationMode: CompilationMode; - }, -): PluginOptions { - const environment = parseConfigPragmaEnvironmentForTest(pragma); - let compilationMode: CompilationMode = defaults.compilationMode; - let panicThreshold: PanicThresholdOptions = 'all_errors'; - let noEmit: boolean = defaultOptions.noEmit; - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - switch (token) { - case '@compilationMode(annotation)': { - compilationMode = 'annotation'; - break; - } - case '@compilationMode(infer)': { - compilationMode = 'infer'; - break; - } - case '@compilationMode(all)': { - compilationMode = 'all'; - break; - } - case '@compilationMode(syntax)': { - compilationMode = 'syntax'; - break; - } - case '@panicThreshold(none)': { - panicThreshold = 'none'; - break; - } - case '@noEmit': { - noEmit = true; - break; - } - } - } - return parsePluginOptions({ - environment, - compilationMode, - panicThreshold, - noEmit, - }); -} - export type PartialEnvironmentConfig = Partial; export type ReactFunctionType = 'Component' | 'Hook' | 'Other'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts new file mode 100644 index 0000000000..e221481724 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts @@ -0,0 +1,206 @@ +/** + * 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 {fromZodError} from 'zod-validation-error'; +import {CompilerError} from '../CompilerError'; +import { + CompilationMode, + defaultOptions, + PanicThresholdOptions, + parsePluginOptions, + PluginOptions, +} from '../Entrypoint'; +import {EnvironmentConfig} from '..'; +import { + EnvironmentConfigSchema, + PartialEnvironmentConfig, +} from '../HIR/Environment'; + +/** + * For test fixtures and playground only. + * + * Pragmas are straightforward to parse for boolean options (`:true` and + * `:false`). These are 'enabled' config values for non-boolean configs (i.e. + * what is used when parsing `:true`). + */ +const testComplexConfigDefaults: PartialEnvironmentConfig = { + validateNoCapitalizedCalls: [], + enableChangeDetectionForDebugging: { + source: 'react-compiler-runtime', + importSpecifierName: '$structuralCheck', + }, + enableEmitFreeze: { + source: 'react-compiler-runtime', + importSpecifierName: 'makeReadOnly', + }, + enableEmitInstrumentForget: { + fn: { + source: 'react-compiler-runtime', + importSpecifierName: 'useRenderCounter', + }, + gating: { + source: 'react-compiler-runtime', + importSpecifierName: 'shouldInstrument', + }, + globalGating: 'DEV', + }, + enableEmitHookGuards: { + source: 'react-compiler-runtime', + importSpecifierName: '$dispatcherGuard', + }, + inlineJsxTransform: { + elementSymbol: 'react.transitional.element', + globalDevVar: 'DEV', + }, + lowerContextAccess: { + source: 'react-compiler-runtime', + importSpecifierName: 'useContext_withSelector', + }, + inferEffectDependencies: [ + { + function: { + source: 'react', + importSpecifierName: 'useEffect', + }, + numRequiredArgs: 1, + }, + { + function: { + source: 'shared-runtime', + importSpecifierName: 'useSpecialEffect', + }, + numRequiredArgs: 2, + }, + { + function: { + source: 'useEffectWrapper', + importSpecifierName: 'default', + }, + numRequiredArgs: 1, + }, + ], +}; + +/** + * For snap test fixtures and playground only. + */ +function parseConfigPragmaEnvironmentForTest( + pragma: string, +): EnvironmentConfig { + const maybeConfig: any = {}; + // Get the defaults to programmatically check for boolean properties + const defaultConfig = EnvironmentConfigSchema.parse({}); + + for (const token of pragma.split(' ')) { + if (!token.startsWith('@')) { + continue; + } + const keyVal = token.slice(1); + let [key, val = undefined] = keyVal.split(':'); + const isSet = val === undefined || val === 'true'; + + if (isSet && key in testComplexConfigDefaults) { + maybeConfig[key] = + testComplexConfigDefaults[key as keyof PartialEnvironmentConfig]; + continue; + } + + if (key === 'customMacros' && val) { + const valSplit = val.split('.'); + if (valSplit.length > 0) { + const props = []; + for (const elt of valSplit.slice(1)) { + if (elt === '*') { + props.push({type: 'wildcard'}); + } else if (elt.length > 0) { + props.push({type: 'name', name: elt}); + } + } + maybeConfig[key] = [[valSplit[0], props]]; + } + continue; + } + + if ( + key !== 'enableResetCacheOnSourceFileChanges' && + typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean' + ) { + // skip parsing non-boolean properties + continue; + } + if (val === undefined || val === 'true') { + maybeConfig[key] = true; + } else { + maybeConfig[key] = false; + } + } + const config = EnvironmentConfigSchema.safeParse(maybeConfig); + if (config.success) { + /** + * Unless explicitly enabled, do not insert HMR handling code + * in test fixtures or playground to reduce visual noise. + */ + if (config.data.enableResetCacheOnSourceFileChanges == null) { + config.data.enableResetCacheOnSourceFileChanges = false; + } + return config.data; + } + CompilerError.invariant(false, { + reason: 'Internal error, could not parse config from pragma string', + description: `${fromZodError(config.error)}`, + loc: null, + suggestions: null, + }); +} +export function parseConfigPragmaForTests( + pragma: string, + defaults: { + compilationMode: CompilationMode; + }, +): PluginOptions { + const environment = parseConfigPragmaEnvironmentForTest(pragma); + let compilationMode: CompilationMode = defaults.compilationMode; + let panicThreshold: PanicThresholdOptions = 'all_errors'; + let noEmit: boolean = defaultOptions.noEmit; + for (const token of pragma.split(' ')) { + if (!token.startsWith('@')) { + continue; + } + switch (token) { + case '@compilationMode(annotation)': { + compilationMode = 'annotation'; + break; + } + case '@compilationMode(infer)': { + compilationMode = 'infer'; + break; + } + case '@compilationMode(all)': { + compilationMode = 'all'; + break; + } + case '@compilationMode(syntax)': { + compilationMode = 'syntax'; + break; + } + case '@panicThreshold(none)': { + panicThreshold = 'none'; + break; + } + case '@noEmit': { + noEmit = true; + break; + } + } + } + return parsePluginOptions({ + environment, + compilationMode, + panicThreshold, + noEmit, + }); +} From 2844b06b90845e9513a1b1983e0fe5772cff16e4 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 7 May 2025 17:46:45 -0400 Subject: [PATCH 368/422] [compiler][playground][tests] Standardize more pragmas (Almost) all pragmas are now one of the following: - `@...TestOnly`: custom pragma for test fixtures - `@` | `@:true`: enables with either true or a default enabled value - `@:` --- .../compilationMode-all-output.txt | 4 +- .../compilationMode-infer-output.txt | 2 +- .../playground/__tests__/e2e/page.spec.ts | 4 +- .../src/Entrypoint/Options.ts | 4 +- .../src/HIR/index.ts | 1 - .../src/Utils/TestUtils.ts | 145 ++++++++++-------- ...ow-function-with-implicit-return.expect.md | 4 +- .../arrow-function-with-implicit-return.js | 2 +- ...ass-component-with-render-helper.expect.md | 4 +- .../class-component-with-render-helper.js | 2 +- .../codegen-instrument-forget-test.expect.md | 4 +- .../codegen-instrument-forget-test.js | 2 +- ...component-declaration-basic.flow.expect.md | 2 +- .../component-declaration-basic.flow.js | 2 +- ...nflict-codegen-instrument-forget.expect.md | 4 +- .../conflict-codegen-instrument-forget.js | 2 +- ...ut-on-suppression-of-custom-rule.expect.md | 4 +- ...r.bailout-on-suppression-of-custom-rule.js | 2 +- ...d-ref-prop-in-render-destructure.expect.md | 4 +- ...lid-read-ref-prop-in-render-destructure.js | 2 +- ...ref-prop-in-render-property-load.expect.md | 4 +- ...d-read-ref-prop-in-render-property-load.js | 2 +- ...invalid-write-ref-prop-in-render.expect.md | 2 +- .../error.invalid-write-ref-prop-in-render.js | 2 +- ...ted-function-in-unreachable-code.expect.md | 2 +- ...do-hoisted-function-in-unreachable-code.js | 2 +- ...ror.validate-blocklisted-imports.expect.md | 4 +- .../error.validate-blocklisted-imports.ts | 2 +- ...-dont-refresh-const-changes-prod.expect.md | 4 +- ...refresh-dont-refresh-const-changes-prod.js | 2 +- ...esh-refresh-on-const-changes-dev.expect.md | 8 +- ...st-refresh-refresh-on-const-changes-dev.js | 2 +- ...en-instrument-forget-gating-test.expect.md | 4 +- .../codegen-instrument-forget-gating-test.js | 2 +- ...ing-test-export-default-function.expect.md | 4 +- .../gating-test-export-default-function.js | 2 +- ...test-export-function-and-default.expect.md | 4 +- ...gating-test-export-function-and-default.js | 2 +- .../gating-test-export-function.expect.md | 4 +- .../gating/gating-test-export-function.js | 2 +- .../compiler/gating/gating-test.expect.md | 4 +- .../fixtures/compiler/gating/gating-test.js | 2 +- ...ion-expression-React-memo-gating.expect.md | 4 +- ...r-function-expression-React-memo-gating.js | 2 +- .../hook-declaration-basic.flow.expect.md | 2 +- .../compiler/hook-declaration-basic.flow.js | 2 +- ...idx-method-no-outlining-wildcard.expect.md | 4 +- .../idx-method-no-outlining-wildcard.js | 2 +- .../idx-method-no-outlining.expect.md | 4 +- .../compiler/idx-method-no-outlining.js | 2 +- .../compiler/idx-no-outlining.expect.md | 4 +- .../fixtures/compiler/idx-no-outlining.js | 2 +- ...mpile-hooks-with-multiple-params.expect.md | 4 +- ...nfer-compile-hooks-with-multiple-params.js | 2 +- ...-components-with-multiple-params.expect.md | 4 +- ...compile-components-with-multiple-params.js | 2 +- ...e-in-non-react-fn-default-import.expect.md | 2 +- ...callsite-in-non-react-fn-default-import.js | 2 +- .../error.callsite-in-non-react-fn.expect.md | 2 +- .../error.callsite-in-non-react-fn.js | 2 +- .../error.non-inlined-effect-fn.expect.md | 2 +- .../error.non-inlined-effect-fn.js | 2 +- ...mport-default-property-useEffect.expect.md | 2 +- ....todo-import-default-property-useEffect.js | 2 +- .../bailout-retry/error.todo-syntax.expect.md | 2 +- .../bailout-retry/error.todo-syntax.js | 2 +- .../bailout-retry/error.use-no-memo.expect.md | 2 +- .../bailout-retry/error.use-no-memo.js | 2 +- ...-after-useeffect-granular-access.expect.md | 4 +- .../mutate-after-useeffect-granular-access.js | 2 +- ...utate-after-useeffect-ref-access.expect.md | 4 +- .../mutate-after-useeffect-ref-access.js | 2 +- .../mutate-after-useeffect.expect.md | 4 +- .../bailout-retry/mutate-after-useeffect.js | 2 +- .../infer-function-React-memo.expect.md | 4 +- .../compiler/infer-function-React-memo.js | 2 +- .../infer-function-assignment.expect.md | 4 +- .../compiler/infer-function-assignment.js | 2 +- ...er-function-expression-component.expect.md | 4 +- .../infer-function-expression-component.js | 2 +- .../infer-function-forwardRef.expect.md | 4 +- .../compiler/infer-function-forwardRef.js | 2 +- ...nctions-component-with-hook-call.expect.md | 4 +- ...nfer-functions-component-with-hook-call.js | 2 +- ...fer-functions-component-with-jsx.expect.md | 4 +- .../infer-functions-component-with-jsx.js | 2 +- ...functions-component-with-ref-arg.expect.md | 4 +- .../infer-functions-component-with-ref-arg.js | 2 +- ...er-functions-hook-with-hook-call.expect.md | 4 +- .../infer-functions-hook-with-hook-call.js | 2 +- .../infer-functions-hook-with-jsx.expect.md | 4 +- .../compiler/infer-functions-hook-with-jsx.js | 2 +- .../infer-nested-object-method.expect.md | 4 +- .../compiler/infer-nested-object-method.jsx | 2 +- .../infer-no-component-annot.expect.md | 4 +- .../compiler/infer-no-component-annot.ts | 2 +- .../infer-no-component-nested-jsx.expect.md | 4 +- .../compiler/infer-no-component-nested-jsx.js | 2 +- .../infer-no-component-obj-return.expect.md | 4 +- .../compiler/infer-no-component-obj-return.js | 2 +- ...-components-without-hooks-or-jsx.expect.md | 4 +- ...er-skip-components-without-hooks-or-jsx.js | 2 +- ...in-catch-in-outer-try-with-catch.expect.md | 8 +- ...id-jsx-in-catch-in-outer-try-with-catch.js | 2 +- .../invalid-jsx-in-try-with-catch.expect.md | 8 +- .../compiler/invalid-jsx-in-try-with-catch.js | 2 +- ...setState-in-useEffect-transitive.expect.md | 8 +- ...nvalid-setState-in-useEffect-transitive.js | 2 +- .../invalid-setState-in-useEffect.expect.md | 8 +- .../compiler/invalid-setState-in-useEffect.js | 2 +- .../compiler/log-pruned-memoization.expect.md | 8 +- .../compiler/log-pruned-memoization.js | 2 +- .../repro-cx-assigned-to-temporary.expect.md | 4 +- .../repro-cx-assigned-to-temporary.js | 2 +- ...-namespace-assigned-to-temporary.expect.md | 4 +- ...epro-cx-namespace-assigned-to-temporary.js | 2 +- ...iple-components-first-is-invalid.expect.md | 4 +- .../multiple-components-first-is-invalid.js | 2 +- .../props-method-dependency.expect.md | 4 +- .../compiler/props-method-dependency.js | 2 +- ...ro-dont-add-hook-guards-on-retry.expect.md | 2 +- .../repro-dont-add-hook-guards-on-retry.js | 2 +- ...repro-retain-source-when-bailout.expect.md | 4 +- .../repro-retain-source-when-bailout.js | 2 +- ...ion-expression-object-expression.expect.md | 2 +- ...d-function-expression-object-expression.js | 2 +- ...lid-hook-in-nested-object-method.expect.md | 2 +- ...or.invalid-hook-in-nested-object-method.js | 2 +- .../rules-of-hooks-0592bd574811.expect.md | 4 +- .../rules-of-hooks-0592bd574811.js | 2 +- .../rules-of-hooks-2bec02ac982b.expect.md | 4 +- .../rules-of-hooks-2bec02ac982b.js | 2 +- .../rules-of-hooks-33a6e23edac1.expect.md | 4 +- .../rules-of-hooks-33a6e23edac1.js | 2 +- .../rules-of-hooks-8f1c2c3f71c9.expect.md | 4 +- .../rules-of-hooks-8f1c2c3f71c9.js | 2 +- .../rules-of-hooks-fe6042f7628b.expect.md | 4 +- .../rules-of-hooks-fe6042f7628b.js | 2 +- ...hout-compilation-annotation-mode.expect.md | 4 +- ...out-without-compilation-annotation-mode.js | 2 +- ...t-without-compilation-infer-mode.expect.md | 4 +- ...-bailout-without-compilation-infer-mode.js | 2 +- ...-constructed-component-in-render.expect.md | 10 +- ...mically-constructed-component-in-render.js | 2 +- ...ly-construct-component-in-render.expect.md | 10 +- ...namically-construct-component-in-render.js | 2 +- ...y-constructed-component-function.expect.md | 10 +- ...amically-constructed-component-function.js | 2 +- ...onstructed-component-method-call.expect.md | 10 +- ...cally-constructed-component-method-call.js | 2 +- ...ically-constructed-component-new.expect.md | 10 +- ...d-dynamically-constructed-component-new.js | 2 +- .../target-flag-meta-internal.expect.md | 2 +- .../fixtures/compiler/target-flag.expect.md | 2 +- .../bailout-capitalized-fn-call.expect.md | 4 +- .../bailout-capitalized-fn-call.js | 2 +- .../bailout-eslint-suppressions.expect.md | 4 +- .../bailout-eslint-suppressions.js | 2 +- .../bailout-validate-preserve-memo.expect.md | 4 +- .../bailout-validate-preserve-memo.js | 2 +- .../bailout-validate-prop-write.expect.md | 4 +- .../bailout-validate-prop-write.js | 2 +- ...lout-validate-ref-current-access.expect.md | 2 +- .../bailout-validate-ref-current-access.js | 2 +- .../bailout-retry/error.todo-syntax.expect.md | 2 +- .../bailout-retry/error.todo-syntax.js | 2 +- ...ror.untransformed-fire-reference.expect.md | 2 +- .../error.untransformed-fire-reference.js | 2 +- .../bailout-retry/error.use-no-memo.expect.md | 2 +- .../bailout-retry/error.use-no-memo.js | 2 +- .../infer-deps-on-retry.expect.md | 4 +- .../bailout-retry/infer-deps-on-retry.js | 2 +- ...-fire-todo-syntax-shouldnt-throw.expect.md | 4 +- .../no-fire-todo-syntax-shouldnt-throw.js | 2 +- ...ailout-validate-conditional-hook.expect.md | 4 +- .../bailout-validate-conditional-hook.js | 2 +- ...ro-dont-add-hook-guards-on-retry.expect.md | 2 +- .../repro-dont-add-hook-guards-on-retry.js | 2 +- ...suppression-skips-all-components.expect.md | 4 +- ...eslint-suppression-skips-all-components.js | 2 +- ...ule-scope-usememo-function-scope.expect.md | 4 +- ...emo-module-scope-usememo-function-scope.js | 2 +- .../babel-plugin-react-compiler/src/index.ts | 2 +- compiler/packages/snap/src/compiler.ts | 112 +------------- compiler/packages/snap/src/runner-worker.ts | 2 +- 185 files changed, 361 insertions(+), 459 deletions(-) diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt index 5633cf0b0f..11f6b17d32 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt @@ -1,5 +1,5 @@ import { c as _c } from "react/compiler-runtime"; //  -        @compilationMode(all) +        @compilationMode:"all" function nonReactFn() {   const $ = _c(1);   let t0; @@ -10,4 +10,4 @@ function nonReactFn() {     t0 = $[0];   }   return t0; -} \ No newline at end of file +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt index b50c37fc4e..563a566a06 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function nonReactFn() {   return {}; } \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 48bb0eed4e..296f45a277 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -92,7 +92,7 @@ function useFoo(propVal: {+baz: number}) { }, { name: 'compilationMode-infer', - input: `// @compilationMode(infer) + input: `// @compilationMode:"infer" function nonReactFn() { return {}; } @@ -101,7 +101,7 @@ function nonReactFn() { }, { name: 'compilationMode-all', - input: `// @compilationMode(all) + input: `// @compilationMode:"all" function nonReactFn() { return {}; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 0adbf3077c..c732e16410 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -98,7 +98,7 @@ export type PluginOptions = { * provided rules will skip compilation. To disable this feature (never bailout of compilation * even if the default ESLint is suppressed), pass an empty array. */ - eslintSuppressionRules?: Array | null | undefined; + eslintSuppressionRules: Array | null | undefined; flowSuppressions: boolean; /* @@ -106,7 +106,7 @@ export type PluginOptions = { */ ignoreUseNoForget: boolean; - sources?: Array | ((filename: string) => boolean) | null; + sources: Array | ((filename: string) => boolean) | null; /** * The compiler has customized support for react-native-reanimated, intended as a temporary workaround. diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts index 579c525dfb..bbc9b325d4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts @@ -17,7 +17,6 @@ export {buildReactiveScopeTerminalsHIR} from './BuildReactiveScopeTerminalsHIR'; export {computeDominatorTree, computePostDominatorTree} from './Dominator'; export { Environment, - parseConfigPragmaForTests, validateEnvironmentConfig, type EnvironmentConfig, type ExternalFunction, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts index e221481724..5485fee559 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts @@ -10,7 +10,6 @@ import {CompilerError} from '../CompilerError'; import { CompilationMode, defaultOptions, - PanicThresholdOptions, parsePluginOptions, PluginOptions, } from '../Entrypoint'; @@ -19,14 +18,28 @@ import { EnvironmentConfigSchema, PartialEnvironmentConfig, } from '../HIR/Environment'; +import {Err, Ok, Result} from './Result'; +import {hasOwnProperty} from './utils'; + +function tryParseTestPragmaValue(val: string): Result { + try { + let parsedVal: unknown; + const stringMatch = /^"([^"]*)"$/.exec(val); + if (stringMatch && stringMatch.length > 1) { + parsedVal = stringMatch[1]; + } else { + parsedVal = JSON.parse(val); + } + return Ok(parsedVal); + } catch (e) { + return Err(e); + } +} + +function isValidEnvironmentConfig(key: string): key is keyof EnvironmentConfig { + return key in EnvironmentConfigSchema.shape; +} -/** - * For test fixtures and playground only. - * - * Pragmas are straightforward to parse for boolean options (`:true` and - * `:false`). These are 'enabled' config values for non-boolean configs (i.e. - * what is used when parsing `:true`). - */ const testComplexConfigDefaults: PartialEnvironmentConfig = { validateNoCapitalizedCalls: [], enableChangeDetectionForDebugging: { @@ -84,34 +97,39 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = { }, ], }; - /** * For snap test fixtures and playground only. */ function parseConfigPragmaEnvironmentForTest( pragma: string, ): EnvironmentConfig { - const maybeConfig: any = {}; - // Get the defaults to programmatically check for boolean properties - const defaultConfig = EnvironmentConfigSchema.parse({}); + const maybeConfig: Partial> = {}; for (const token of pragma.split(' ')) { if (!token.startsWith('@')) { continue; } const keyVal = token.slice(1); - let [key, val = undefined] = keyVal.split(':'); + const valIdx = keyVal.indexOf(':'); + const key = valIdx === -1 ? keyVal : keyVal.slice(0, valIdx); + const val = valIdx === -1 ? undefined : keyVal.slice(valIdx + 1); const isSet = val === undefined || val === 'true'; - - if (isSet && key in testComplexConfigDefaults) { - maybeConfig[key] = - testComplexConfigDefaults[key as keyof PartialEnvironmentConfig]; + if (!isValidEnvironmentConfig(key)) { continue; } - if (key === 'customMacros' && val) { - const valSplit = val.split('.'); - if (valSplit.length > 0) { + if (isSet && key in testComplexConfigDefaults) { + maybeConfig[key] = testComplexConfigDefaults[key]; + continue; + } else if (isSet) { + maybeConfig[key] = true; + continue; + } else if (val === 'false') { + maybeConfig[key] = false; + } else if (val) { + const parsedVal = tryParseTestPragmaValue(val).unwrap(); + if (key === 'customMacros' && typeof parsedVal === 'string') { + const valSplit = parsedVal.split('.'); const props = []; for (const elt of valSplit.slice(1)) { if (elt === '*') { @@ -121,21 +139,9 @@ function parseConfigPragmaEnvironmentForTest( } } maybeConfig[key] = [[valSplit[0], props]]; + continue; } - continue; - } - - if ( - key !== 'enableResetCacheOnSourceFileChanges' && - typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean' - ) { - // skip parsing non-boolean properties - continue; - } - if (val === undefined || val === 'true') { - maybeConfig[key] = true; - } else { - maybeConfig[key] = false; + maybeConfig[key] = parsedVal; } } const config = EnvironmentConfigSchema.safeParse(maybeConfig); @@ -156,6 +162,13 @@ function parseConfigPragmaEnvironmentForTest( suggestions: null, }); } + +const testComplexPluginOptionDefaults: Partial = { + gating: { + source: 'ReactForgetFeatureFlag', + importSpecifierName: 'isForgetEnabled_Fixtures', + }, +}; export function parseConfigPragmaForTests( pragma: string, defaults: { @@ -163,44 +176,42 @@ export function parseConfigPragmaForTests( }, ): PluginOptions { const environment = parseConfigPragmaEnvironmentForTest(pragma); - let compilationMode: CompilationMode = defaults.compilationMode; - let panicThreshold: PanicThresholdOptions = 'all_errors'; - let noEmit: boolean = defaultOptions.noEmit; + const options: Record = { + ...defaultOptions, + panicThreshold: 'all_errors', + compilationMode: defaults.compilationMode, + environment, + }; for (const token of pragma.split(' ')) { if (!token.startsWith('@')) { continue; } - switch (token) { - case '@compilationMode(annotation)': { - compilationMode = 'annotation'; - break; - } - case '@compilationMode(infer)': { - compilationMode = 'infer'; - break; - } - case '@compilationMode(all)': { - compilationMode = 'all'; - break; - } - case '@compilationMode(syntax)': { - compilationMode = 'syntax'; - break; - } - case '@panicThreshold(none)': { - panicThreshold = 'none'; - break; - } - case '@noEmit': { - noEmit = true; - break; + const keyVal = token.slice(1); + const idx = keyVal.indexOf(':'); + const key = idx === -1 ? keyVal : keyVal.slice(0, idx); + const val = idx === -1 ? undefined : keyVal.slice(idx + 1); + if (!hasOwnProperty(defaultOptions, key)) { + continue; + } + const isSet = val === undefined || val === 'true'; + if (isSet && key in testComplexPluginOptionDefaults) { + options[key] = testComplexPluginOptionDefaults[key]; + continue; + } else if (isSet) { + options[key] = true; + } else if (val === 'false') { + options[key] = false; + } else if (val != null) { + const parsedVal = tryParseTestPragmaValue(val).unwrap(); + if (key === 'target' && parsedVal === 'donotuse_meta_internal') { + options[key] = { + kind: parsedVal, + runtimeModule: 'react', + }; + } else { + options[key] = parsedVal; } } } - return parsePluginOptions({ - environment, - compilationMode, - panicThreshold, - noEmit, - }); + return parsePluginOptions(options); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md index 244e70bbba..d50d9d58cc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" const Test = () =>
; export const FIXTURE_ENTRYPOINT = { @@ -15,7 +15,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" const Test = () => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js index d2b29b1889..03195fd7d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" const Test = () =>
; export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md index 11abaa66ee..093a88b1de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" class Component { _renderMessage = () => { const Message = () => { @@ -22,7 +22,7 @@ class Component { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" class Component { _renderMessage = () => { const Message = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js index 90cba5b265..0786c82d6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" class Component { _renderMessage = () => { const Message = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md index 8c2f0b94ef..edabebecfc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" function Bar(props) { 'use forget'; @@ -24,7 +24,7 @@ function Foo(props) { ```javascript import { shouldInstrument, useRenderCounter } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode:"annotation" function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js index 2aef527e6b..cc5e471054 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js @@ -1,4 +1,4 @@ -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md index 12184dafbb..15cba5b29a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default component Foo(bar: number) { return ; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js index d29777d4f0..04d419c7ce 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js @@ -1,4 +1,4 @@ -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default component Foo(bar: number) { return ; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md index a2df134b2a..243c415467 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" import {identity} from 'shared-runtime'; @@ -35,7 +35,7 @@ import { shouldInstrument as _shouldInstrument3, useRenderCounter, } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode:"annotation" import { identity } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js index 88ffefe220..58729d6bee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js @@ -1,4 +1,4 @@ -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" import {identity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index 75da05a26e..d74ebd119c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @eslintSuppressionRules(my-app/react-rule) +// @eslintSuppressionRules:["my-app","react-rule"] /* eslint-disable my-app/react-rule */ function lowercasecomponent() { @@ -19,7 +19,7 @@ function lowercasecomponent() { ## Error ``` - 1 | // @eslintSuppressionRules(my-app/react-rule) + 1 | // @eslintSuppressionRules:["my-app","react-rule"] 2 | > 3 | /* eslint-disable my-app/react-rule */ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. eslint-disable my-app/react-rule (3:3) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js index 96d08827d5..331e551596 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js @@ -1,4 +1,4 @@ -// @eslintSuppressionRules(my-app/react-rule) +// @eslintSuppressionRules:["my-app","react-rule"] /* eslint-disable my-app/react-rule */ function lowercasecomponent() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md index 100dafaf38..6b63344a76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component({ref}) { const value = ref.current; return
{value}
; @@ -14,7 +14,7 @@ function Component({ref}) { ## Error ``` - 1 | // @validateRefAccessDuringRender @compilationMode(infer) + 1 | // @validateRefAccessDuringRender @compilationMode:"infer" 2 | function Component({ref}) { > 3 | const value = ref.current; | ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js index c5276b79b2..81c58cf252 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js @@ -1,4 +1,4 @@ -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component({ref}) { const value = ref.current; return
{value}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md index 4d8c4c735c..c49aea8740 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const value = props.ref.current; return
{value}
; @@ -14,7 +14,7 @@ function Component(props) { ## Error ``` - 1 | // @validateRefAccessDuringRender @compilationMode(infer) + 1 | // @validateRefAccessDuringRender @compilationMode:"infer" 2 | function Component(props) { > 3 | const value = props.ref.current; | ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js index c31a081366..abb1ff86fb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js @@ -1,4 +1,4 @@ -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const value = props.ref.current; return
{value}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md index c00aaca4fe..5038379283 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const ref = props.ref; ref.current = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js index b939ea540b..32997b8978 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js @@ -1,4 +1,4 @@ -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const ref = props.ref; ref.current = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md index 1d88b8d50c..2b9092213b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js index e23016cc49..d67232371d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md index a4a885d94b..68b275e34c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateBlocklistedImports(DangerousImport) +// @validateBlocklistedImports:["DangerousImport"] import {foo} from 'DangerousImport'; import {useIdentity} from 'shared-runtime'; @@ -17,7 +17,7 @@ function useHook() { ## Error ``` - 1 | // @validateBlocklistedImports(DangerousImport) + 1 | // @validateBlocklistedImports:["DangerousImport"] > 2 | import {foo} from 'DangerousImport'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Bailing out due to blocklisted import. Import from module DangerousImport (2:2) 3 | import {useIdentity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts index fcde4a1922..d33c175bcf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts @@ -1,4 +1,4 @@ -// @validateBlocklistedImports(DangerousImport) +// @validateBlocklistedImports:["DangerousImport"] import {foo} from 'DangerousImport'; import {useIdentity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md index 14bc703149..df2f4b01a9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; @@ -43,7 +43,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { useEffect, useMemo, useState } from "react"; import { ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js index 0bc38808b9..f1d96b5635 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md index 1af628811b..56a7289ae8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) @enableResetCacheOnSourceFileChanges +// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; @@ -46,7 +46,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableResetCacheOnSourceFileChanges +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableResetCacheOnSourceFileChanges import { useEffect, useMemo, useState } from "react"; import { ValidateMemoization } from "shared-runtime"; @@ -63,12 +63,12 @@ function unsafeUpdateConst() { function Component() { const $ = _c(3); if ( - $[0] !== "8d7015668f857996c3d895a7a90e3e16b8a791d5b9cd13f2c76e1c254aeedebb" + $[0] !== "a585d27423c1181e7b6305ff909458183d284658c3c3d2e3764e1128be302fd7" ) { for (let $i = 0; $i < 3; $i += 1) { $[$i] = Symbol.for("react.memo_cache_sentinel"); } - $[0] = "8d7015668f857996c3d895a7a90e3e16b8a791d5b9cd13f2c76e1c254aeedebb"; + $[0] = "a585d27423c1181e7b6305ff909458183d284658c3c3d2e3764e1128be302fd7"; } useState(_temp); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js index cc5c1a4abf..9130f13c15 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) @enableResetCacheOnSourceFileChanges +// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md index 6256e5ec90..5eaa593fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitInstrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode:"annotation" @gating function Bar(props) { 'use forget'; @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { shouldInstrument, useRenderCounter } from "react-compiler-runtime"; import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode:"annotation" @gating const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js index 6730630ac3..425b99da8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js @@ -1,4 +1,4 @@ -// @enableEmitInstrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode:"annotation" @gating function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md index ae7896e7a5..fbe3c554c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js index 1ec9a902b2..369dc3bbd0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md index 4c368a8254..8665addb8a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; @@ -35,7 +35,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js index 5b7f7c81b7..af2f098130 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md index c3e2e4a042..9661fe3d2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export function Bar(props) { 'use forget'; return
{props.bar}
; @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" export const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js index 5bcf9ac67d..e07144b160 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md index 8ccce56713..0cd67fd1d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" function Bar(props) { 'use forget'; return
{props.bar}
; @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js index 3bb92654d9..bc45933af3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md index aff21ee0bd..1106722259 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(infer) +// @gating @compilationMode:"infer" import React from 'react'; export default React.forwardRef(function notNamedLikeAComponent(props) { return
; @@ -14,7 +14,7 @@ export default React.forwardRef(function notNamedLikeAComponent(props) { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(infer) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"infer" import React from "react"; export default React.forwardRef( isForgetEnabled_Fixtures() diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js index 79acdd821a..518c8eeee3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(infer) +// @gating @compilationMode:"infer" import React from 'react'; export default React.forwardRef(function notNamedLikeAComponent(props) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md index f75514e8a8..d23e808983 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default hook useFoo(bar: number) { return [bar]; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js index 217fbe1636..13db3325ce 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js @@ -1,4 +1,4 @@ -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default hook useFoo(bar: number) { return [bar]; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md index dbc90978d5..59875c8d2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @customMacros(idx.*.b) +// @customMacros:"idx.*.b" function Component(props) { // outlined @@ -31,7 +31,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @customMacros(idx.*.b) +import { c as _c } from "react/compiler-runtime"; // @customMacros:"idx.*.b" function Component(props) { const $ = _c(16); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js index 4b944ddcca..5a362bbbe7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js @@ -1,4 +1,4 @@ -// @customMacros(idx.*.b) +// @customMacros:"idx.*.b" function Component(props) { // outlined diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md index f1bced727b..9c748d84be 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @customMacros(idx.a) +// @customMacros:"idx.a" function Component(props) { // outlined @@ -25,7 +25,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @customMacros(idx.a) +import { c as _c } from "react/compiler-runtime"; // @customMacros:"idx.a" function Component(props) { const $ = _c(10); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js index f5b034b91d..1b2cadb5d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js @@ -1,4 +1,4 @@ -// @customMacros(idx.a) +// @customMacros:"idx.a" function Component(props) { // outlined diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md index a0fafc56c8..880cdcdb21 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @customMacros(idx) +// @customMacros:"idx" import idx from 'idx'; function Component(props) { @@ -21,7 +21,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @customMacros(idx) +import { c as _c } from "react/compiler-runtime"; // @customMacros:"idx" function Component(props) { var _ref2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js index 7d16c8d2b7..8cdf205ddd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js @@ -1,4 +1,4 @@ -// @customMacros(idx) +// @customMacros:"idx" import idx from 'idx'; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md index 4263d8db8d..129d3a72e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useNoAlias} from 'shared-runtime'; // This should be compiled by Forget @@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { useNoAlias } from "shared-runtime"; // This should be compiled by Forget diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js index e354c67bb2..a03aa10aa8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useNoAlias} from 'shared-runtime'; // This should be compiled by Forget diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md index d0999fca0c..9b35a83801 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Takes multiple parameters - not a component! function Component(foo, bar) { return
; @@ -18,7 +18,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Takes multiple parameters - not a component! function Component(foo, bar) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js index 9b2cf36a19..442c367fde 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Takes multiple parameters - not a component! function Component(foo, bar) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md index cd977ae834..8cbe8da62f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import useMyEffect from 'useEffectWrapper'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js index 8061b44a3d..992cd828ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import useMyEffect from 'useEffectWrapper'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md index e36e9b3c0e..8feec25376 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import {useEffect} from 'react'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js index 813c3b6c3d..106a3c7352 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import {useEffect} from 'react'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md index ee17fb048e..eeec00995b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js index a011e3bf14..dac029e0ca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md index 71ecefeeea..416ede4556 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import React from 'react'; function NonReactiveDepInEffect() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js index 237535fe5b..b1b0e8d2fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import React from 'react'; function NonReactiveDepInEffect() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md index 575fd2101f..a396b9c3ca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useSpecialEffect} from 'shared-runtime'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js index d3c83c2562..2c53d1a21b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useSpecialEffect} from 'shared-runtime'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md index 52b99a393e..c3413f9fd0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({propVal}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js index 53eeda5050..d00ce2ac98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({propVal}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md index 7099388378..701f54b663 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; import {print} from 'shared-runtime'; @@ -20,7 +20,7 @@ function Component({foo}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useEffect } from "react"; import { print } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js index fe00af3922..f1b3de4f7f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index d0532a495a..05ef40c150 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -19,7 +19,7 @@ function Component({arrRef}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useEffect, useRef } from "react"; import { print } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js index ff2cda6b89..f497d7e595 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index 5da3ceca22..fa1df3ef88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({foo}) { @@ -17,7 +17,7 @@ function Component({foo}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useEffect } from "react"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js index 806ca5322f..2e2eb7bc08 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({foo}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md index 8038a8d071..89a89c41e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" React.memo(props => { return
; }); @@ -12,7 +12,7 @@ React.memo(props => { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" React.memo((props) => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js index a8da0532df..d9e1eaf31a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" React.memo(props => { return
; }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md index 92bbaf7c30..c183bf949c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" const Component = props => { return
; }; @@ -12,7 +12,7 @@ const Component = props => { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" const Component = (props) => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js index 6dff8028b9..c12f61d18f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" const Component = props => { return
; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md index 24a7050c68..49886e0ac3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" const Component = function ComponentName(props) { return ; @@ -13,7 +13,7 @@ const Component = function ComponentName(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" const Component = function ComponentName(props) { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js index 7fe4c9c0cc..deac307a3b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" const Component = function ComponentName(props) { return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md index 011006b2aa..ca1d6e4336 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" React.forwardRef(props => { return
; }); @@ -12,7 +12,7 @@ React.forwardRef(props => { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" React.forwardRef((props) => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js index c0abf753c2..f2b458df8d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" React.forwardRef(props => { return
; }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md index 51c9a14abf..d92f1a6c45 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const [state, _] = useState(null); return [state]; @@ -13,7 +13,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function Component(props) { const $ = _c(2); const [state] = useState(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js index 791542cb34..57c03cd222 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const [state, _] = useState(null); return [state]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md index 96cae6ee6a..ed175c61ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { return
; } @@ -12,7 +12,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function Component(props) { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js index bb552b5f2a..73607ce519 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { return
; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md index c19248a0c0..5f3da3fd8e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Foo({}, ref) { return
; @@ -18,7 +18,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function Foo(t0, ref) { const $ = _c(2); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js index aaf9492528..cefd53b123 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Foo({}, ref) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md index d1ed626a97..a94055469a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function useStateValue(props) { const [state, _] = useState(null); return [state]; @@ -13,7 +13,7 @@ function useStateValue(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function useStateValue(props) { const $ = _c(2); const [state] = useState(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js index ed90449f36..7191fe6cb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function useStateValue(props) { const [state, _] = useState(null); return [state]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md index dfc6fdbc2e..3f4c0a0173 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function useDiv(props) { return
; } @@ -12,7 +12,7 @@ function useDiv(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function useDiv(props) { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js index 3453fc8286..2fef05df39 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function useDiv(props) { return
; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md index 4c89f59919..963bbc7d3e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {Stringify} from 'shared-runtime'; @@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx index c8e396b057..cb9d22a9f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md index b5710f5840..1334ab1f0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useIdentity, identity} from 'shared-runtime'; function Component(fakeProps: number) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import { useIdentity, identity } from "shared-runtime"; function Component(fakeProps: number) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts index 81ec3e34e3..e628f9c685 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useIdentity, identity} from 'shared-runtime'; function Component(fakeProps: number) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md index adb9a1da40..28672a1da1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const result = f(props); function helper() { @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const result = f(props); function helper() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js index 9d9e070ca9..11a21ab56e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const result = f(props); function helper() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md index 70782b6aab..27d7ee1c1e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const ignore = ; return {foo: f(props)}; @@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const ignore = ; return { foo: f(props) }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js index f48e2b1e15..950988ef63 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const ignore = ; return {foo: f(props)}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md index 0637082e50..d4d4c1bc0e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This component is skipped bc it doesn't call any hooks or // use JSX: function Component(props) { @@ -14,7 +14,7 @@ function Component(props) { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This component is skipped bc it doesn't call any hooks or // use JSX: function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js index 5f6aa674b2..c1396aca94 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // This component is skipped bc it doesn't call any hooks or // use JSX: function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md index 2e052ecf33..9f4d85de70 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements import {identity} from 'shared-runtime'; function Component(props) { @@ -25,7 +25,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoJSXInTryStatements +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoJSXInTryStatements import { identity } from "shared-runtime"; function Component(props) { @@ -65,8 +65,8 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":11,"column":11,"index":214},"end":{"line":11,"column":32,"index":235},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":83},"end":{"line":17,"column":1,"index":290},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":17,"column":1,"index":298},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js index 2e5a3618b4..3cf5cc9b8a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js @@ -1,4 +1,4 @@ -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements import {identity} from 'shared-runtime'; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md index 702d317a4b..9fe89f25ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements function Component(props) { let el; try { @@ -18,7 +18,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoJSXInTryStatements +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoJSXInTryStatements function Component(props) { const $ = _c(1); let el; @@ -42,8 +42,8 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":5,"column":9,"index":96},"end":{"line":5,"column":16,"index":103},"filename":"invalid-jsx-in-try-with-catch.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":41},"end":{"line":10,"column":1,"index":152},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":49},"end":{"line":10,"column":1,"index":160},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js index 815fb4ae9e..d01a93bf5a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js @@ -1,4 +1,4 @@ -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements function Component(props) { let el; try { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index d220617dc1..e116fb4fd9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { @@ -24,7 +24,7 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoSetStateInPassiveEffects +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInPassiveEffects import { useEffect, useState } from "react"; function Component() { @@ -65,8 +65,8 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":264},"end":{"line":13,"column":5,"index":265},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":16,"column":1,"index":292},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":272},"end":{"line":13,"column":5,"index":273},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":99},"end":{"line":16,"column":1,"index":300},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js index b03c08609a..bbf976f0bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js @@ -1,4 +1,4 @@ -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index 667f57b7cc..202071455b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { @@ -18,7 +18,7 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoSetStateInPassiveEffects +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInPassiveEffects import { useEffect, useState } from "react"; function Component() { @@ -45,8 +45,8 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":179},"end":{"line":7,"column":12,"index":187},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":10,"column":1,"index":224},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":187},"end":{"line":7,"column":12,"index":195},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":99},"end":{"line":10,"column":1,"index":232},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js index e806b359c6..e526ad82e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js @@ -1,4 +1,4 @@ -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md index 99796806bc..b6583dbc9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger +// @loggerTestOnly import {createContext, use, useState} from 'react'; import { Stringify, @@ -56,7 +56,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly import { createContext, use, useState } from "react"; import { Stringify, @@ -129,8 +129,8 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileSuccess","fnLoc":{"start":{"line":10,"column":0,"index":159},"end":{"line":33,"column":1,"index":903},"filename":"log-pruned-memoization.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":2,"prunedMemoValues":3} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":37,"column":0,"index":939},"end":{"line":43,"column":1,"index":1037},"filename":"log-pruned-memoization.ts"},"fnName":"Wrapper","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":10,"column":0,"index":167},"end":{"line":33,"column":1,"index":911},"filename":"log-pruned-memoization.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":2,"prunedMemoValues":3} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":37,"column":0,"index":947},"end":{"line":43,"column":1,"index":1045},"filename":"log-pruned-memoization.ts"},"fnName":"Wrapper","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js index d0097b4eb3..42c39d1e71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js @@ -1,4 +1,4 @@ -// @logger +// @loggerTestOnly import {createContext, use, useState} from 'react'; import { Stringify, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md index 55ffec877d..08a3ccb87f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros:"cx" import {identity} from 'shared-runtime'; const DARK = 'dark'; @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros:"cx" import { identity } from "shared-runtime"; const DARK = "dark"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js index ac6a475e0c..c72f693754 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros:"cx" import {identity} from 'shared-runtime'; const DARK = 'dark'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md index aeae54128b..c99b05a113 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) import {identity} from 'shared-runtime'; const DARK = 'dark'; @@ -49,7 +49,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) import { identity } from "shared-runtime"; const DARK = "dark"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js index fe7abb7cdf..4254c5a442 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) import {identity} from 'shared-runtime'; const DARK = 'dark'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md index 3224997b40..13e67f0e49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" import {useHook} from 'shared-runtime'; function InvalidComponent(props) { @@ -21,7 +21,7 @@ function ValidComponent(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @panicThreshold(none) +import { c as _c } from "react/compiler-runtime"; // @panicThreshold:"none" import { useHook } from "shared-runtime"; function InvalidComponent(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js index 6a3d52c864..80f9bdd109 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js @@ -1,4 +1,4 @@ -// @panicThreshold(none) +// @panicThreshold:"none" import {useHook} from 'shared-runtime'; function InvalidComponent(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md index 3297892ea2..4c5da20170 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useMemo} from 'react'; import {ValidateMemoization} from 'shared-runtime'; @@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { useMemo } from "react"; import { ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js index 4c2d322ad3..5882e3cc6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useMemo} from 'react'; import {ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md index 6477654d3d..b19a06519f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire component Foo(useDynamicHook) { useDynamicHook(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js index d0313aa42f..54b1818213 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js @@ -1,4 +1,4 @@ -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire component Foo(useDynamicHook) { useDynamicHook(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md index ae9edc478e..a153a5cf74 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" import {useNoAlias} from 'shared-runtime'; const cond = true; @@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" import { useNoAlias } from "shared-runtime"; const cond = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js index ffed061d99..fc96e1435f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js @@ -1,4 +1,4 @@ -// @panicThreshold(none) +// @panicThreshold:"none" import {useNoAlias} from 'shared-runtime'; const cond = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md index 7394c17b9c..2d1860bc3b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const f = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js index 240926d979..bfe82523a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const f = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md index 4d1e7e530c..7403afcaf7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const x = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js index 54d4a89d46..779cad41f9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const x = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md index 4356c33c9a..82b6998634 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Regression test for some internal code. // This shows how the "callback rule" is more relaxed, // and doesn't kick in unless we're confident we're in @@ -20,7 +20,7 @@ function makeListener(instance) { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Regression test for some internal code. // This shows how the "callback rule" is more relaxed, // and doesn't kick in unless we're confident we're in diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js index f83f535928..360593a570 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Regression test for some internal code. // This shows how the "callback rule" is more relaxed, // and doesn't kick in unless we're confident we're in diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md index 9a6ca0d249..f5bd1a042a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can call hooks. function createHook() { return function useHook() { @@ -16,7 +16,7 @@ function createHook() { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can call hooks. function createHook() { return function useHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js index f27e6bcf5f..fcdf80d224 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can call hooks. function createHook() { return function useHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md index ea8869bacd..11330a9c4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can use hooks. function createHook() { return function useHookWithHook() { @@ -15,7 +15,7 @@ function createHook() { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can use hooks. function createHook() { return function useHookWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js index ce6244346f..3f3a4c0576 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can use hooks. function createHook() { return function useHookWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md index dfb4fa6df7..c0f5ad939d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because components can use hooks. function createComponentWithHook() { return function ComponentWithHook() { @@ -15,7 +15,7 @@ function createComponentWithHook() { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because components can use hooks. function createComponentWithHook() { return function ComponentWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js index 2aced0739c..31a5d85356 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because components can use hooks. function createComponentWithHook() { return function ComponentWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md index 1c676d9825..ce42d44a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This is valid because "use"-prefixed functions called in // unnamed function arguments are not assumed to be hooks. unknownFunction(function (foo, bar) { @@ -16,7 +16,7 @@ unknownFunction(function (foo, bar) { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This is valid because "use"-prefixed functions called in // unnamed function arguments are not assumed to be hooks. unknownFunction(function (foo, bar) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js index a480e74a83..b7744c5ff7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // This is valid because "use"-prefixed functions called in // unnamed function arguments are not assumed to be hooks. unknownFunction(function (foo, bar) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md index 6103aa5094..bed1b9e601 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @panicThreshold(none) @compilationMode(annotation) +// @gating @panicThreshold:"none" @compilationMode:"annotation" let someGlobal = 'joe'; function Component() { @@ -21,7 +21,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @gating @panicThreshold(none) @compilationMode(annotation) +// @gating @panicThreshold:"none" @compilationMode:"annotation" let someGlobal = "joe"; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js index 1a350a1df7..8d0264cd17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js @@ -1,4 +1,4 @@ -// @gating @panicThreshold(none) @compilationMode(annotation) +// @gating @panicThreshold:"none" @compilationMode:"annotation" let someGlobal = 'joe'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md index abbf9aff13..36b91bcf2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @panicThreshold(none) @compilationMode(infer) +// @gating @panicThreshold:"none" @compilationMode:"infer" let someGlobal = 'joe'; function Component() { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @gating @panicThreshold(none) @compilationMode(infer) +// @gating @panicThreshold:"none" @compilationMode:"infer" let someGlobal = "joe"; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js index dedabda9d6..1de4cb4490 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js @@ -1,4 +1,4 @@ -// @gating @panicThreshold(none) @compilationMode(infer) +// @gating @panicThreshold:"none" @compilationMode:"infer" let someGlobal = 'joe'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md index 836d0fe7d9..af8103b7ae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { let Component; if (props.cond) { @@ -18,7 +18,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(3); let Component; @@ -50,9 +50,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":10,"index":194},"end":{"line":9,"column":19,"index":203},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":5,"column":16,"index":116},"end":{"line":5,"column":33,"index":133},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":10,"column":1,"index":209},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":10,"column":1,"index":217},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js index 580d330728..6ff7ff321c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { let Component; if (props.cond) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md index a222bd44ca..7720863da3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = createComponent(); return ; @@ -13,7 +13,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(1); let t0; @@ -32,9 +32,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":112},"end":{"line":4,"column":19,"index":121},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":37,"index":100},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":127},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":135},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js index ebbb2b239d..209cc83d65 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = createComponent(); return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md index 41339bd8c4..8d218bf24b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { function Component() { return
; @@ -15,7 +15,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(1); let t0; @@ -37,9 +37,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":6,"column":10,"index":122},"end":{"line":6,"column":19,"index":131},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":2,"index":65},"end":{"line":5,"column":3,"index":111},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":7,"column":1,"index":137},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":7,"column":1,"index":145},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js index 1ce24193d2..e48712dbf7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { function Component() { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md index c53cf894cd..e3bc7a5eb5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = props.foo.bar(); return ; @@ -13,7 +13,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(4); let t0; @@ -41,9 +41,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":110},"end":{"line":4,"column":19,"index":119},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":35,"index":98},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":125},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":133},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js index 01c3fb85f9..7f43ffacbd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = props.foo.bar(); return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md index d45f263adb..02e9f4f4a4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = new ComponentFactory(); return ; @@ -13,7 +13,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(1); let t0; @@ -32,9 +32,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":117},"end":{"line":4,"column":19,"index":126},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":42,"index":105},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":132},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":140},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js index 4d5ba62d1e..a735e076d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = new ComponentFactory(); return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md index acf34a474c..6c81defa1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md @@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react"; // @target="donotuse_meta_internal" +import { c as _c } from "react/compiler-runtime"; // @target="donotuse_meta_internal" function Component() { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md index b4cf41e369..b27b786d57 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md @@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react-compiler-runtime"; // @target="18" +import { c as _c } from "react/compiler-runtime"; // @target="18" function Component() { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md index 77bdd31275..b11be5668d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateNoCapitalizedCalls @enableFire @panicThreshold(none) +// @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" import {fire} from 'react'; const CapitalizedCall = require('shared-runtime').sum; @@ -24,7 +24,7 @@ function Component({prop1, bar}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" import { fire } from "react"; const CapitalizedCall = require("shared-runtime").sum; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js index 679945eaab..bd9715e91a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js @@ -1,4 +1,4 @@ -// @validateNoCapitalizedCalls @enableFire @panicThreshold(none) +// @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" import {fire} from 'react'; const CapitalizedCall = require('shared-runtime').sum; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md index 30bd6d42e5..99774bdd3e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {useRef} from 'react'; function Component({props, bar}) { @@ -26,7 +26,7 @@ function Component({props, bar}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { useRef } from "react"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js index 5312e5707c..be89a6d3b9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {useRef} from 'react'; function Component({props, bar}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md index 6477a01126..1a22df2941 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none) +// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold:"none" import {fire} from 'react'; import {sum} from 'shared-runtime'; @@ -24,7 +24,7 @@ function Component({prop1, bar}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold:"none" import { fire } from "react"; import { sum } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js index c3bb8b4216..4ba2de0eb1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none) +// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold:"none" import {fire} from 'react'; import {sum} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md index e6ce051f10..0f1d745537 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; function Component({prop1}) { @@ -20,7 +20,7 @@ function Component({prop1}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { fire } from "react"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js index b0cfd4fe39..6b250a1692 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; function Component({prop1}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md index 79f5a2986d..b55526e921 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @enableFire @panicThreshold(none) +// @flow @enableFire @panicThreshold:"none" import {fire} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js index f649fe5902..1dcb5b5c47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js @@ -1,4 +1,4 @@ -// @flow @enableFire @panicThreshold(none) +// @flow @enableFire @panicThreshold:"none" import {fire} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index c5d7456d65..aa3d989296 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js index b1ee459177..52c224e321 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index ddcc86ff00..0141ffb8ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; console.log(fire == null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js index 25a60a9716..afeead0812 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; console.log(fire == null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index 84a27b43b8..275012351c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js index 92e1ff211a..685309a250 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md index 42c800f8e5..36ed7a3d36 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useRef} from 'react'; import {useSpecialEffect} from 'shared-runtime'; @@ -25,7 +25,7 @@ function useFoo({cond}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useRef } from "react"; import { useSpecialEffect } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js index f3a57bb912..4f6b908b53 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useRef} from 'react'; import {useSpecialEffect} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md index 7ba4ee2811..06268ea854 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** @@ -43,7 +43,7 @@ function FireComponent(props) { ## Code ```javascript -import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { fire } from "react"; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js index 899fa33376..e11ab6b6de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md index dde2b692f4..2c81d693b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire, useEffect} from 'react'; import {Stringify} from 'shared-runtime'; @@ -29,7 +29,7 @@ function Component(props) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { fire, useEffect } from "react"; import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js index fa8c034bfb..602f121ad7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire, useEffect} from 'react'; import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md index c7ed50ceba..75c46e196e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire import {useEffect, fire} from 'react'; function Component(props, useDynamicHook) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js index 077982e8d4..5fc11c9a82 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js @@ -1,4 +1,4 @@ -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire import {useEffect, fire} from 'react'; function Component(props, useDynamicHook) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md index f0c6bce342..2a419a6415 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" // unclosed disable rule should affect all components /* eslint-disable react-hooks/rules-of-hooks */ @@ -20,7 +20,7 @@ function ValidComponent2(props) { ## Code ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" // unclosed disable rule should affect all components /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js index 121f100418..a3e2bab77e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js @@ -1,4 +1,4 @@ -// @panicThreshold(none) +// @panicThreshold:"none" // unclosed disable rule should affect all components /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md index 69c1b9bbbb..375a38664e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(all) +// @compilationMode:"all" 'use no memo'; function TestComponent({x}) { @@ -15,7 +15,7 @@ function TestComponent({x}) { ## Code ```javascript -// @compilationMode(all) +// @compilationMode:"all" "use no memo"; function TestComponent({ x }) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js index 9b314e1f99..21eec7ab2c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -1,4 +1,4 @@ -// @compilationMode(all) +// @compilationMode:"all" 'use no memo'; function TestComponent({x}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 60865b8aa7..086e010fea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -30,7 +30,6 @@ export { export { Effect, ValueKind, - parseConfigPragmaForTests, printHIR, printFunctionWithOutlined, validateEnvironmentConfig, @@ -43,6 +42,7 @@ export { printReactiveFunction, printReactiveFunctionWithOutlined, } from './ReactiveScopes'; +export {parseConfigPragmaForTests} from './Utils/TestUtils'; declare global { let __DEV__: boolean | null | undefined; } diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 6fce644542..0cadf30bf0 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -19,11 +19,7 @@ import type { CompilerPipelineValue, } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; -import type { - Macro, - MacroMethod, - parseConfigPragmaForTests as ParseConfigPragma, -} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils'; import * as HermesParser from 'hermes-parser'; import invariant from 'invariant'; import path from 'path'; @@ -47,52 +43,10 @@ function makePluginOptions( EffectEnum: typeof Effect, ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { - let gating = null; - let hookPattern: string | null = null; // TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false let validatePreserveExistingMemoizationGuarantees = false; - let customMacros: null | Array = null; - let validateBlocklistedImports = null; let target: CompilerReactTarget = '19'; - if (firstLine.includes('@gating')) { - gating = { - source: 'ReactForgetFeatureFlag', - importSpecifierName: 'isForgetEnabled_Fixtures', - }; - } - - const targetMatch = /@target="([^"]+)"/.exec(firstLine); - if (targetMatch) { - if (targetMatch[1] === 'donotuse_meta_internal') { - target = { - kind: targetMatch[1], - runtimeModule: 'react', - }; - } else { - // @ts-ignore - target = targetMatch[1]; - } - } - - let eslintSuppressionRules: Array | null = null; - const eslintSuppressionMatch = /@eslintSuppressionRules\(([^)]+)\)/.exec( - firstLine, - ); - if (eslintSuppressionMatch != null) { - eslintSuppressionRules = eslintSuppressionMatch[1].split('|'); - } - - let flowSuppressions: boolean = false; - if (firstLine.includes('@enableFlowSuppressions')) { - flowSuppressions = true; - } - - let ignoreUseNoForget: boolean = false; - if (firstLine.includes('@ignoreUseNoForget')) { - ignoreUseNoForget = true; - } - /** * Snap currently runs all fixtures without `validatePreserveExistingMemo` as * most fixtures are interested in compilation output, not whether the @@ -105,64 +59,9 @@ function makePluginOptions( validatePreserveExistingMemoizationGuarantees = true; } - const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine); - if ( - hookPatternMatch && - hookPatternMatch.length > 1 && - hookPatternMatch[1].trim().length > 0 - ) { - hookPattern = hookPatternMatch[1].trim(); - } else if (firstLine.includes('@hookPattern')) { - throw new Error( - 'Invalid @hookPattern:"..." pragma, must contain the prefix between balanced double quotes eg @hookPattern:"pattern"', - ); - } - - const customMacrosMatch = /@customMacros\(([^)]+)\)/.exec(firstLine); - if ( - customMacrosMatch && - customMacrosMatch.length > 1 && - customMacrosMatch[1].trim().length > 0 - ) { - const customMacrosStrs = customMacrosMatch[1] - .split(' ') - .map(s => s.trim()) - .filter(s => s.length > 0); - if (customMacrosStrs.length > 0) { - customMacros = []; - for (const customMacroStr of customMacrosStrs) { - const props: Array = []; - const customMacroSplit = customMacroStr.split('.'); - if (customMacroSplit.length > 0) { - for (const elt of customMacroSplit.slice(1)) { - if (elt === '*') { - props.push({type: 'wildcard'}); - } else if (elt.length > 0) { - props.push({type: 'name', name: elt}); - } - } - customMacros.push([customMacroSplit[0], props]); - } - } - } - } - - const validateBlocklistedImportsMatch = - /@validateBlocklistedImports\(([^)]+)\)/.exec(firstLine); - if ( - validateBlocklistedImportsMatch && - validateBlocklistedImportsMatch.length > 1 && - validateBlocklistedImportsMatch[1].trim().length > 0 - ) { - validateBlocklistedImports = validateBlocklistedImportsMatch[1] - .split(' ') - .map(s => s.trim()) - .filter(s => s.length > 0); - } - const logs: Array<{filename: string | null; event: LoggerEvent}> = []; const logger: Logger = { - logEvent: firstLine.includes('@logger') + logEvent: firstLine.includes('@loggerTestOnly') ? (filename, event) => { logs.push({filename, event}); } @@ -179,17 +78,10 @@ function makePluginOptions( EffectEnum, ValueKindEnum, }), - customMacros, assertValidMutableRanges: true, - hookPattern, validatePreserveExistingMemoizationGuarantees, - validateBlocklistedImports, }, logger, - gating, - eslintSuppressionRules, - flowSuppressions, - ignoreUseNoForget, enableReanimatedCheck: false, target, }; diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index a72acf34db..2478e6a545 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -7,7 +7,7 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {PluginObj} from '@babel/core'; -import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils'; import type {printFunctionWithOutlined as PrintFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import type {printReactiveFunctionWithOutlined as PrintReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; import {TransformResult, transformFixtureInput} from './compiler'; From b61fea3bd36320a50e48997ee855117878b3a6a3 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 7 May 2025 17:46:45 -0400 Subject: [PATCH 369/422] [compiler][be] Make program traversal more readable React Compiler's program traversal logic is pretty lengthy and complex as we've added a lot of features piecemeal. `compileProgram` is 300+ lines long and has confusing control flow (defining helpers inline, invoking visitors, mutating-asts-while-iterating, mutating global `ALREADY_COMPILED` state). - Moved more stuff to `ProgramContext` - Separated `compileProgram` into a bunch of helpers Will come back and add more comments inline --- .../src/Entrypoint/Imports.ts | 62 +- .../src/Entrypoint/Program.ts | 560 ++++++++++-------- .../ValidateNoUntransformedReferences.ts | 6 +- 3 files changed, 363 insertions(+), 265 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 704f696b3b..d5cac921e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -18,8 +18,9 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; -import {CompilerReactTarget} from './Options'; -import {getReactCompilerRuntimeModule} from './Program'; +import {LoggerEvent, PluginOptions} from './Options'; +import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( path: NodePath, @@ -52,32 +53,61 @@ export function validateRestrictedImports( } } +type ProgramContextOptions = { + program: NodePath; + suppressions: Array; + opts: PluginOptions; + filename: string | null; + code: string | null; +}; export class ProgramContext { - /* Program and environment context */ + /** + * Program and environment context + */ scope: BabelScope; + opts: PluginOptions; + filename: string | null; + code: string | null; reactRuntimeModule: string; - hookPattern: string | null; + suppressions: Array; + /* + * This is a hack to work around what seems to be a Babel bug. Babel doesn't + * consistently respect the `skip()` function to avoid revisiting a node within + * a pass, so we use this set to track nodes that we have compiled. + */ + alreadyCompiled: WeakSet | Set = new (WeakSet ?? Set)(); // known generated or referenced identifiers in the program knownReferencedNames: Set = new Set(); // generated imports imports: Map> = new Map(); - constructor( - program: NodePath, - reactRuntimeModule: CompilerReactTarget, - hookPattern: string | null, - ) { - this.hookPattern = hookPattern; + /** + * Metadata from compilation + */ + retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; + inferredEffectLocations: Set = new Set(); + + constructor({ + program, + suppressions, + opts, + filename, + code, + }: ProgramContextOptions) { this.scope = program.scope; - this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule); + this.opts = opts; + this.filename = filename; + this.code = code; + this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); + this.suppressions = suppressions; } isHookName(name: string): boolean { - if (this.hookPattern == null) { + if (this.opts.environment.hookPattern == null) { return isHookName(name); } else { - const match = new RegExp(this.hookPattern).exec(name); + const match = new RegExp(this.opts.environment.hookPattern).exec(name); return ( match != null && typeof match[1] === 'string' && isHookName(match[1]) ); @@ -179,6 +209,12 @@ export class ProgramContext { }); return Err(error); } + + logEvent(event: LoggerEvent): void { + if (this.opts.logger != null) { + this.opts.logger.logEvent(this.filename, event); + } + } } function getExistingImports( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 34347f10b8..58e3c7de01 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment'; +import {ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -43,17 +43,21 @@ export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); export function findDirectiveEnablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), + ) ?? null ); } export function findDirectiveDisablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_OUT_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_OUT_DIRECTIVES.has(directive.value.value), + ) ?? null ); } @@ -88,13 +92,16 @@ export type CompileResult = { function logError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - if (pass.opts.logger) { + if (context.opts.logger) { if (err instanceof CompilerError) { for (const detail of err.details) { - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, detail: detail.options, @@ -108,7 +115,7 @@ function logError( stringifiedError = err?.toString() ?? '[ null ]'; } - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'PipelineError', fnLoc, data: stringifiedError, @@ -118,13 +125,17 @@ function logError( } function handleError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - logError(err, pass, fnLoc); + logError(err, context, fnLoc); if ( - pass.opts.panicThreshold === 'all_errors' || - (pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) || + context.opts.panicThreshold === 'all_errors' || + (context.opts.panicThreshold === 'critical_errors' && + isCriticalError(err)) || isConfigError(err) // Always throws regardless of panic threshold ) { throw err; @@ -187,7 +198,6 @@ export function createNewFunctionNode( } } // Avoid visiting the new transformed version - ALREADY_COMPILED.add(transformedFn); return transformedFn; } @@ -239,13 +249,6 @@ function insertNewOutlinedFunctionNode( } } -/* - * This is a hack to work around what seems to be a Babel bug. Babel doesn't - * consistently respect the `skip()` function to avoid revisiting a node within - * a pass, so we use this set to track nodes that we have compiled. - */ -const ALREADY_COMPILED: WeakSet | Set = new (WeakSet ?? Set)(); - const DEFAULT_ESLINT_SUPPRESSIONS = [ 'react-hooks/exhaustive-deps', 'react-hooks/rules-of-hooks', @@ -268,41 +271,43 @@ function isFilePartOfSources( return false; } -export type CompileProgramResult = { +export type CompileProgramMetadata = { retryErrors: Array<{fn: BabelFn; error: CompilerError}>; inferredEffectLocations: Set; }; /** - * `compileProgram` is directly invoked by the react-compiler babel plugin, so - * exceptions thrown by this function will fail the babel build. - * - call `handleError` if your error is recoverable. - * Unless the error is a warning / info diagnostic, compilation of a function - * / entire file should also be skipped. - * - throw an exception if the error is fatal / not recoverable. - * Examples of this are invalid compiler configs or failure to codegen outlined - * functions *after* already emitting optimized components / hooks that invoke - * the outlined functions. + * Main entrypoint for React Compiler. + * + * @param program The Babel program node to compile + * @param pass Compiler configuration and context + * @returns Compilation results or null if compilation was skipped */ export function compileProgram( program: NodePath, pass: CompilerPass, -): CompileProgramResult | null { +): CompileProgramMetadata | null { + /** + * This is directly invoked by the react-compiler babel plugin, so exceptions + * thrown by this function will fail the babel build. + * - call `handleError` if your error is recoverable. + * Unless the error is a warning / info diagnostic, compilation of a function + * / entire file should also be skipped. + * - throw an exception if the error is fatal / not recoverable. + * Examples of this are invalid compiler configs or failure to codegen outlined + * functions *after* already emitting optimized components / hooks that invoke + * the outlined functions. + */ if (shouldSkipCompilation(program, pass)) { return null; } - - const environment = pass.opts.environment; - const restrictedImportsErr = validateRestrictedImports(program, environment); + const restrictedImportsErr = validateRestrictedImports( + program, + pass.opts.environment, + ); if (restrictedImportsErr) { handleError(restrictedImportsErr, pass, null); return null; } - - const programContext = new ProgramContext( - program, - pass.opts.target, - environment.hookPattern, - ); /* * Record lint errors and critical errors as depending on Forget's config, * we may still need to run Forget's analysis on every function (even if we @@ -313,16 +318,88 @@ export function compileProgram( pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions, ); - const queue: Array<{ - kind: 'original' | 'outlined'; - fn: BabelFn; - fnType: ReactFunctionType; - }> = []; + + const programContext = new ProgramContext({ + program: program, + opts: pass.opts, + filename: pass.filename, + code: pass.code, + suppressions, + }); + + const queue: Array = findFunctionsToCompile( + program, + pass, + programContext, + ); const compiledFns: Array = []; + while (queue.length !== 0) { + const current = queue.shift()!; + const compiled = processFn(current.fn, current.fnType, programContext); + + if (compiled != null) { + for (const outlined of compiled.outlined) { + CompilerError.invariant(outlined.fn.outlined.length === 0, { + reason: 'Unexpected nested outlined functions', + loc: outlined.fn.loc, + }); + const fn = insertNewOutlinedFunctionNode( + program, + current.fn, + outlined.fn, + ); + fn.skip(); + programContext.alreadyCompiled.add(fn.node); + if (outlined.type !== null) { + queue.push({ + kind: 'outlined', + fn, + fnType: outlined.type, + }); + } + } + compiledFns.push({ + kind: current.kind, + originalFn: current.fn, + compiledFn: compiled, + }); + } + } + + // Avoid modifying the program if we find a program level opt-out + if (findDirectiveDisablingMemoization(program.node.directives) != null) { + return null; + } + + // Insert React Compiler generated functions into the Babel AST + applyCompiledFunctions(program, compiledFns, pass, programContext); + + return { + retryErrors: programContext.retryErrors, + inferredEffectLocations: programContext.inferredEffectLocations, + }; +} + +type CompileSource = { + kind: 'original' | 'outlined'; + fn: BabelFn; + fnType: ReactFunctionType; +}; +/** + * Find all React components and hooks that need to be compiled + * + * @returns An array of React functions from @param program to transform + */ +function findFunctionsToCompile( + program: NodePath, + pass: CompilerPass, + programContext: ProgramContext, +): Array { + const queue: Array = []; const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => { - const fnType = getReactFunctionType(fn, pass, environment); - if (fnType === null || ALREADY_COMPILED.has(fn.node)) { + const fnType = getReactFunctionType(fn, pass); + if (fnType === null || programContext.alreadyCompiled.has(fn.node)) { return; } @@ -331,7 +408,7 @@ export function compileProgram( * traversal will loop infinitely. * Ensure we avoid visiting the original function again. */ - ALREADY_COMPILED.add(fn.node); + programContext.alreadyCompiled.add(fn.node); fn.skip(); queue.push({kind: 'original', fn, fnType}); @@ -346,7 +423,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, ClassExpression(node: NodePath) { @@ -355,7 +431,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, FunctionDeclaration: traverseFunction, @@ -370,205 +445,196 @@ export function compileProgram( filename: pass.filename ?? null, }, ); - const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; - const inferredEffectLocations = new Set(); - const processFn = ( - fn: BabelFn, - fnType: ReactFunctionType, - ): null | CodegenFunction => { - let optInDirectives: Array = []; - let optOutDirectives: Array = []; - if (fn.node.body.type === 'BlockStatement') { - optInDirectives = findDirectiveEnablingMemoization( - fn.node.body.directives, - ); - optOutDirectives = findDirectiveDisablingMemoization( - fn.node.body.directives, - ); - } + return queue; +} - /** - * Note that Babel does not attach comment nodes to nodes; they are dangling off of the - * Program node itself. We need to figure out whether an eslint suppression range - * applies to this function first. - */ - const suppressionsInFunction = filterSuppressionsThatAffectFunction( - suppressions, - fn, - ); - let compileResult: - | {kind: 'compile'; compiledFn: CodegenFunction} - | {kind: 'error'; error: unknown}; - if (suppressionsInFunction.length > 0) { - compileResult = { - kind: 'error', - error: suppressionsToCompilerError(suppressionsInFunction), - }; +/** + * Try to compile a source function, taking into account all local suppressions, + * opt-ins, and opt-outs. + * + * Errors encountered during compilation are either logged (if recoverable) or + * thrown (if non-recoverable). + * + * @returns the compiled function or null if the function was skipped (due to + * config settings and/or outputs) + */ +function processFn( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): null | CodegenFunction { + let directives; + if (fn.node.body.type !== 'BlockStatement') { + directives = {optIn: null, optOut: null}; + } else { + directives = { + optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + }; + } + + const compileResult = tryCompileFunction(fn, fnType, programContext); + + if (compileResult.kind === 'error') { + if (directives.optOut != null) { + logError(compileResult.error, programContext, fn.node.loc ?? null); } else { - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'all_features', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - } catch (err) { - compileResult = {kind: 'error', error: err}; - } + handleError(compileResult.error, programContext, fn.node.loc ?? null); } - - if (compileResult.kind === 'error') { - /** - * If an opt out directive is present, log only instead of throwing and don't mark as - * containing a critical error. - */ - if (optOutDirectives.length > 0) { - logError(compileResult.error, pass, fn.node.loc ?? null); - } else { - handleError(compileResult.error, pass, fn.node.loc ?? null); - } - // If non-memoization features are enabled, retry regardless of error kind - if ( - !(environment.enableFire || environment.inferEffectDependencies != null) - ) { - return null; - } - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'no_inferred_memo', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - if ( - !compileResult.compiledFn.hasFireRewrite && - !compileResult.compiledFn.hasInferredEffect - ) { - return null; - } - } catch (err) { - // TODO: we might want to log error here, but this will also result in duplicate logging - if (err instanceof CompilerError) { - retryErrors.push({fn, error: err}); - } - return null; - } - } - - /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. - */ - if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) { - for (const directive of optOutDirectives) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSkip', - fnLoc: fn.node.body.loc ?? null, - reason: `Skipped due to '${directive.value.value}' directive.`, - loc: directive.loc ?? null, - }); - } - return null; - } - - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSuccess', - fnLoc: fn.node.loc ?? null, - fnName: compileResult.compiledFn.id?.name ?? null, - memoSlots: compileResult.compiledFn.memoSlotsUsed, - memoBlocks: compileResult.compiledFn.memoBlocks, - memoValues: compileResult.compiledFn.memoValues, - prunedMemoBlocks: compileResult.compiledFn.prunedMemoBlocks, - prunedMemoValues: compileResult.compiledFn.prunedMemoValues, - }); - - /** - * Always compile functions with opt in directives. - */ - if (optInDirectives.length > 0) { - return compileResult.compiledFn; - } else if (pass.opts.compilationMode === 'annotation') { - /** - * No opt-in directive in annotation mode, so don't insert the compiled function. - */ - return null; - } - - if (!pass.opts.noEmit) { - return compileResult.compiledFn; - } - /** - * inferEffectDependencies + noEmit is currently only used for linting. In - * this mode, add source locations for where the compiler *can* infer effect - * dependencies. - */ - for (const loc of compileResult.compiledFn.inferredEffectLocations) { - if (loc !== GeneratedSource) inferredEffectLocations.add(loc); - } - return null; - }; - - while (queue.length !== 0) { - const current = queue.shift()!; - const compiled = processFn(current.fn, current.fnType); - if (compiled === null) { - continue; - } - for (const outlined of compiled.outlined) { - CompilerError.invariant(outlined.fn.outlined.length === 0, { - reason: 'Unexpected nested outlined functions', - loc: outlined.fn.loc, - }); - const fn = insertNewOutlinedFunctionNode( - program, - current.fn, - outlined.fn, - ); - fn.skip(); - ALREADY_COMPILED.add(fn.node); - if (outlined.type !== null) { - queue.push({ - kind: 'outlined', - fn, - fnType: outlined.type, - }); - } - } - compiledFns.push({ - kind: current.kind, - compiledFn: compiled, - originalFn: current.fn, - }); + return retryCompileFunction(fn, fnType, programContext); } /** - * Do not modify source if there is a module scope level opt out directive. + * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler + * for validation but we don't mutate the babel AST. This allows us to flag if there is an + * unused 'use no forget/memo' directive. */ - const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization( - program.node.directives, - ); - if (moduleScopeOptOutDirectives.length > 0) { + if ( + programContext.opts.ignoreUseNoForget === false && + directives.optOut != null + ) { + programContext.logEvent({ + kind: 'CompileSkip', + fnLoc: fn.node.body.loc ?? null, + reason: `Skipped due to '${directives.optOut.value}' directive.`, + loc: directives.optOut.loc ?? null, + }); return null; } - /* - * Only insert Forget-ified functions if we have not encountered a critical - * error elsewhere in the file, regardless of bailout mode. + const compiledFn = compileResult.compiledFn; + programContext.logEvent({ + kind: 'CompileSuccess', + fnLoc: fn.node.loc ?? null, + fnName: compiledFn.id?.name ?? null, + memoSlots: compiledFn.memoSlotsUsed, + memoBlocks: compiledFn.memoBlocks, + memoValues: compiledFn.memoValues, + prunedMemoBlocks: compiledFn.prunedMemoBlocks, + prunedMemoValues: compiledFn.prunedMemoValues, + }); + + /** + * Always compile functions with opt in directives. */ + if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else if (!programContext.opts.noEmit) { + return compiledFn; + } + + /** + * inferEffectDependencies + noEmit is currently only used for linting. In + * this mode, add source locations for where the compiler *can* infer effect + * dependencies. + */ + for (const loc of compileResult.compiledFn.inferredEffectLocations) { + if (loc !== GeneratedSource) { + programContext.inferredEffectLocations.add(loc); + } + } + return null; +} + +function tryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): + | {kind: 'compile'; compiledFn: CodegenFunction} + | {kind: 'error'; error: unknown} { + /** + * Note that Babel does not attach comment nodes to nodes; they are dangling off of the + * Program node itself. We need to figure out whether an eslint suppression range + * applies to this function first. + */ + const suppressionsInFunction = filterSuppressionsThatAffectFunction( + programContext.suppressions, + fn, + ); + if (suppressionsInFunction.length > 0) { + return { + kind: 'error', + error: suppressionsToCompilerError(suppressionsInFunction), + }; + } + + try { + return { + kind: 'compile', + compiledFn: compileFn( + fn, + programContext.opts.environment, + fnType, + 'all_features', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ), + }; + } catch (err) { + return {kind: 'error', error: err}; + } +} + +/** + * If non-memo feature flags are enabled, retry compilation with a more minimal + * feature set. + * + * @returns a CodegenFunction if retry was successful + */ +function retryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): CodegenFunction | null { + const environment = programContext.opts.environment; + if ( + !(environment.enableFire || environment.inferEffectDependencies != null) + ) { + return null; + } + try { + const retryResult = compileFn( + fn, + environment, + fnType, + 'no_inferred_memo', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ); + + if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) { + return null; + } + return retryResult; + } catch (err) { + // TODO: we might want to log error here, but this will also result in duplicate logging + if (err instanceof CompilerError) { + programContext.retryErrors.push({fn, error: err}); + } + return null; + } +} + +/** + * Applies React Compiler generated functions to the babel AST by replacing + * existing functions in place or inserting new declarations. + */ +function applyCompiledFunctions( + program: NodePath, + compiledFns: Array, + pass: CompilerPass, + programContext: ProgramContext, +): void { const referencedBeforeDeclared = pass.opts.gating != null ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) @@ -576,6 +642,7 @@ export function compileProgram( for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); + programContext.alreadyCompiled.add(transformedFn); if (referencedBeforeDeclared != null && kind === 'original') { CompilerError.invariant(pass.opts.gating != null, { @@ -598,7 +665,6 @@ export function compileProgram( if (compiledFns.length > 0) { addImportsToProgram(program, programContext); } - return {retryErrors, inferredEffectLocations}; } function shouldSkipCompilation( @@ -640,14 +706,10 @@ function shouldSkipCompilation( function getReactFunctionType( fn: BabelFn, pass: CompilerPass, - /** - * TODO(mofeiZ): remove once we validate PluginOptions with Zod - */ - environment: EnvironmentConfig, ): ReactFunctionType | null { - const hookPattern = environment.hookPattern; + const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0) + if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 5f6c6986e0..e288c227ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -18,7 +18,7 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {Environment} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; -import {CompileProgramResult} from './Program'; +import {CompileProgramMetadata} from './Program'; function throwInvalidReact( options: Omit, @@ -109,7 +109,7 @@ export default function validateNoUntransformedReferences( filename: string | null, logger: Logger | null, env: EnvironmentConfig, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const moduleLoadChecks = new Map< string, @@ -236,7 +236,7 @@ function transformProgram( moduleLoadChecks: Map>, filename: string | null, logger: Logger | null, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const traversalState: TraversalState = { shouldInvalidateScopes: true, From 1586d8c8f61544685c82d2eb8838ac0eba47e110 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 7 May 2025 17:47:29 -0400 Subject: [PATCH 370/422] [compiler][entrypoint] Fix edgecases for noEmit and opt-outs Title --- .../src/Entrypoint/Imports.ts | 9 +++- .../src/Entrypoint/Program.ts | 47 ++++++++++++------- ...bailout-nopanic-shouldnt-outline.expect.md | 3 -- .../compiler/use-memo-noemit.expect.md | 15 +----- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index d5cac921e9..d8f3ad0904 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -19,7 +19,11 @@ import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; import {LoggerEvent, PluginOptions} from './Options'; -import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import { + BabelFn, + findDirectiveDisablingMemoization, + getReactCompilerRuntimeModule, +} from './Program'; import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( @@ -70,6 +74,7 @@ export class ProgramContext { code: string | null; reactRuntimeModule: string; suppressions: Array; + hasModuleScopeOptOut: boolean; /* * This is a hack to work around what seems to be a Babel bug. Babel doesn't @@ -101,6 +106,8 @@ export class ProgramContext { this.code = code; this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); this.suppressions = suppressions; + this.hasModuleScopeOptOut = + findDirectiveDisablingMemoization(program.node.directives) != null; } isHookName(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 58e3c7de01..6ed9295bc8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -368,7 +368,19 @@ export function compileProgram( } // Avoid modifying the program if we find a program level opt-out - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } return null; } @@ -513,21 +525,6 @@ function processFn( prunedMemoValues: compiledFn.prunedMemoValues, }); - /** - * Always compile functions with opt in directives. - */ - if (directives.optIn != null) { - return compiledFn; - } else if (programContext.opts.compilationMode === 'annotation') { - /** - * If no opt-in directive is found and the compiler is configured in - * annotation mode, don't insert the compiled function. - */ - return null; - } else if (!programContext.opts.noEmit) { - return compiledFn; - } - /** * inferEffectDependencies + noEmit is currently only used for linting. In * this mode, add source locations for where the compiler *can* infer effect @@ -538,7 +535,23 @@ function processFn( programContext.inferredEffectLocations.add(loc); } } - return null; + + /** + * Always compile functions with opt in directives. + */ + if (programContext.hasModuleScopeOptOut || programContext.opts.noEmit) { + return null; + } else if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else { + return compiledFn; + } } function tryCompileFunction( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md index f24d949205..cfbaa34568 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -20,9 +20,6 @@ function Foo() { function Foo() { return ; } -function _temp() { - return alert("hello!"); -} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md index dfc831555f..c47501945b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -19,22 +19,11 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @noEmit +// @noEmit function Foo() { "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return alert("hello!"); + return ; } export const FIXTURE_ENTRYPOINT = { From ad098db7cb7bec71a32b903b05531bd152825730 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 7 May 2025 17:49:19 -0400 Subject: [PATCH 371/422] [compiler][gating] Experimental directive based gating (todo: fill in summary) --- .../src/Entrypoint/Options.ts | 45 ++++++ .../src/Entrypoint/Program.ts | 132 +++++++++++++++--- .../dynamic-gating-bailout-nopanic.expect.md | 66 +++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 +++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 +++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 +++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 ++ 16 files changed, 514 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..ebf9f2467d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,27 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +269,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +318,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 6ed9295bc8..9aca4a1469 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,54 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else { + return Ok( + result.length > 0 + ? { + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + } + : null, + ); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -475,12 +533,31 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we should + * fall back to the safest option which is to not optimize the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -648,25 +725,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -722,8 +805,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.isOk() && optInDirectives.unwrap() !== null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From e6576bb15e8355c98c789634cae9bf609af26e2a Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 11:26:24 -0400 Subject: [PATCH 372/422] [compiler][playground][tests] Standardize more pragmas (Almost) all pragmas are now one of the following: - `@...TestOnly`: custom pragma for test fixtures - `@` | `@:true`: enables with either true or a default enabled value - `@:` --- .../compilationMode-all-output.txt | 2 +- .../compilationMode-infer-output.txt | 2 +- .../playground/__tests__/e2e/page.spec.ts | 4 +- .../src/Entrypoint/Options.ts | 4 +- .../src/Utils/TestUtils.ts | 138 +++++++++--------- ...ow-function-with-implicit-return.expect.md | 4 +- .../arrow-function-with-implicit-return.js | 2 +- ...ass-component-with-render-helper.expect.md | 4 +- .../class-component-with-render-helper.js | 2 +- .../codegen-instrument-forget-test.expect.md | 4 +- .../codegen-instrument-forget-test.js | 2 +- ...component-declaration-basic.flow.expect.md | 2 +- .../component-declaration-basic.flow.js | 2 +- ...nflict-codegen-instrument-forget.expect.md | 4 +- .../conflict-codegen-instrument-forget.js | 2 +- ...ut-on-suppression-of-custom-rule.expect.md | 4 +- ...r.bailout-on-suppression-of-custom-rule.js | 2 +- ...d-ref-prop-in-render-destructure.expect.md | 4 +- ...lid-read-ref-prop-in-render-destructure.js | 2 +- ...ref-prop-in-render-property-load.expect.md | 4 +- ...d-read-ref-prop-in-render-property-load.js | 2 +- ...invalid-write-ref-prop-in-render.expect.md | 2 +- .../error.invalid-write-ref-prop-in-render.js | 2 +- ...ted-function-in-unreachable-code.expect.md | 2 +- ...do-hoisted-function-in-unreachable-code.js | 2 +- ...ror.validate-blocklisted-imports.expect.md | 4 +- .../error.validate-blocklisted-imports.ts | 2 +- ...-dont-refresh-const-changes-prod.expect.md | 4 +- ...refresh-dont-refresh-const-changes-prod.js | 2 +- ...esh-refresh-on-const-changes-dev.expect.md | 8 +- ...st-refresh-refresh-on-const-changes-dev.js | 2 +- ...en-instrument-forget-gating-test.expect.md | 4 +- .../codegen-instrument-forget-gating-test.js | 2 +- ...ing-test-export-default-function.expect.md | 4 +- .../gating-test-export-default-function.js | 2 +- ...test-export-function-and-default.expect.md | 4 +- ...gating-test-export-function-and-default.js | 2 +- .../gating-test-export-function.expect.md | 4 +- .../gating/gating-test-export-function.js | 2 +- .../compiler/gating/gating-test.expect.md | 4 +- .../fixtures/compiler/gating/gating-test.js | 2 +- ...ion-expression-React-memo-gating.expect.md | 4 +- ...r-function-expression-React-memo-gating.js | 2 +- .../hook-declaration-basic.flow.expect.md | 2 +- .../compiler/hook-declaration-basic.flow.js | 2 +- ...idx-method-no-outlining-wildcard.expect.md | 4 +- .../idx-method-no-outlining-wildcard.js | 2 +- .../idx-method-no-outlining.expect.md | 4 +- .../compiler/idx-method-no-outlining.js | 2 +- .../compiler/idx-no-outlining.expect.md | 4 +- .../fixtures/compiler/idx-no-outlining.js | 2 +- ...mpile-hooks-with-multiple-params.expect.md | 4 +- ...nfer-compile-hooks-with-multiple-params.js | 2 +- ...-components-with-multiple-params.expect.md | 4 +- ...compile-components-with-multiple-params.js | 2 +- ...e-in-non-react-fn-default-import.expect.md | 2 +- ...callsite-in-non-react-fn-default-import.js | 2 +- .../error.callsite-in-non-react-fn.expect.md | 2 +- .../error.callsite-in-non-react-fn.js | 2 +- .../error.non-inlined-effect-fn.expect.md | 2 +- .../error.non-inlined-effect-fn.js | 2 +- ...mport-default-property-useEffect.expect.md | 2 +- ....todo-import-default-property-useEffect.js | 2 +- .../bailout-retry/error.todo-syntax.expect.md | 2 +- .../bailout-retry/error.todo-syntax.js | 2 +- .../bailout-retry/error.use-no-memo.expect.md | 2 +- .../bailout-retry/error.use-no-memo.js | 2 +- ...-after-useeffect-granular-access.expect.md | 4 +- .../mutate-after-useeffect-granular-access.js | 2 +- ...utate-after-useeffect-ref-access.expect.md | 4 +- .../mutate-after-useeffect-ref-access.js | 2 +- .../mutate-after-useeffect.expect.md | 4 +- .../bailout-retry/mutate-after-useeffect.js | 2 +- .../infer-function-React-memo.expect.md | 4 +- .../compiler/infer-function-React-memo.js | 2 +- .../infer-function-assignment.expect.md | 4 +- .../compiler/infer-function-assignment.js | 2 +- ...er-function-expression-component.expect.md | 4 +- .../infer-function-expression-component.js | 2 +- .../infer-function-forwardRef.expect.md | 4 +- .../compiler/infer-function-forwardRef.js | 2 +- ...nctions-component-with-hook-call.expect.md | 4 +- ...nfer-functions-component-with-hook-call.js | 2 +- ...fer-functions-component-with-jsx.expect.md | 4 +- .../infer-functions-component-with-jsx.js | 2 +- ...functions-component-with-ref-arg.expect.md | 4 +- .../infer-functions-component-with-ref-arg.js | 2 +- ...er-functions-hook-with-hook-call.expect.md | 4 +- .../infer-functions-hook-with-hook-call.js | 2 +- .../infer-functions-hook-with-jsx.expect.md | 4 +- .../compiler/infer-functions-hook-with-jsx.js | 2 +- .../infer-nested-object-method.expect.md | 4 +- .../compiler/infer-nested-object-method.jsx | 2 +- .../infer-no-component-annot.expect.md | 4 +- .../compiler/infer-no-component-annot.ts | 2 +- .../infer-no-component-nested-jsx.expect.md | 4 +- .../compiler/infer-no-component-nested-jsx.js | 2 +- .../infer-no-component-obj-return.expect.md | 4 +- .../compiler/infer-no-component-obj-return.js | 2 +- ...-components-without-hooks-or-jsx.expect.md | 4 +- ...er-skip-components-without-hooks-or-jsx.js | 2 +- ...in-catch-in-outer-try-with-catch.expect.md | 8 +- ...id-jsx-in-catch-in-outer-try-with-catch.js | 2 +- .../invalid-jsx-in-try-with-catch.expect.md | 8 +- .../compiler/invalid-jsx-in-try-with-catch.js | 2 +- ...setState-in-useEffect-transitive.expect.md | 8 +- ...nvalid-setState-in-useEffect-transitive.js | 2 +- .../invalid-setState-in-useEffect.expect.md | 8 +- .../compiler/invalid-setState-in-useEffect.js | 2 +- .../compiler/log-pruned-memoization.expect.md | 8 +- .../compiler/log-pruned-memoization.js | 2 +- .../repro-cx-assigned-to-temporary.expect.md | 4 +- .../repro-cx-assigned-to-temporary.js | 2 +- ...-namespace-assigned-to-temporary.expect.md | 4 +- ...epro-cx-namespace-assigned-to-temporary.js | 2 +- ...iple-components-first-is-invalid.expect.md | 4 +- .../multiple-components-first-is-invalid.js | 2 +- .../props-method-dependency.expect.md | 4 +- .../compiler/props-method-dependency.js | 2 +- ...ro-dont-add-hook-guards-on-retry.expect.md | 2 +- .../repro-dont-add-hook-guards-on-retry.js | 2 +- ...repro-retain-source-when-bailout.expect.md | 4 +- .../repro-retain-source-when-bailout.js | 2 +- ...ion-expression-object-expression.expect.md | 2 +- ...d-function-expression-object-expression.js | 2 +- ...lid-hook-in-nested-object-method.expect.md | 2 +- ...or.invalid-hook-in-nested-object-method.js | 2 +- .../rules-of-hooks-0592bd574811.expect.md | 4 +- .../rules-of-hooks-0592bd574811.js | 2 +- .../rules-of-hooks-2bec02ac982b.expect.md | 4 +- .../rules-of-hooks-2bec02ac982b.js | 2 +- .../rules-of-hooks-33a6e23edac1.expect.md | 4 +- .../rules-of-hooks-33a6e23edac1.js | 2 +- .../rules-of-hooks-8f1c2c3f71c9.expect.md | 4 +- .../rules-of-hooks-8f1c2c3f71c9.js | 2 +- .../rules-of-hooks-fe6042f7628b.expect.md | 4 +- .../rules-of-hooks-fe6042f7628b.js | 2 +- ...hout-compilation-annotation-mode.expect.md | 4 +- ...out-without-compilation-annotation-mode.js | 2 +- ...t-without-compilation-infer-mode.expect.md | 4 +- ...-bailout-without-compilation-infer-mode.js | 2 +- ...-constructed-component-in-render.expect.md | 10 +- ...mically-constructed-component-in-render.js | 2 +- ...ly-construct-component-in-render.expect.md | 10 +- ...namically-construct-component-in-render.js | 2 +- ...y-constructed-component-function.expect.md | 10 +- ...amically-constructed-component-function.js | 2 +- ...onstructed-component-method-call.expect.md | 10 +- ...cally-constructed-component-method-call.js | 2 +- ...ically-constructed-component-new.expect.md | 10 +- ...d-dynamically-constructed-component-new.js | 2 +- .../target-flag-meta-internal.expect.md | 2 +- .../fixtures/compiler/target-flag.expect.md | 2 +- .../bailout-capitalized-fn-call.expect.md | 4 +- .../bailout-capitalized-fn-call.js | 2 +- .../bailout-eslint-suppressions.expect.md | 4 +- .../bailout-eslint-suppressions.js | 2 +- .../bailout-validate-preserve-memo.expect.md | 4 +- .../bailout-validate-preserve-memo.js | 2 +- .../bailout-validate-prop-write.expect.md | 4 +- .../bailout-validate-prop-write.js | 2 +- ...lout-validate-ref-current-access.expect.md | 2 +- .../bailout-validate-ref-current-access.js | 2 +- .../bailout-retry/error.todo-syntax.expect.md | 2 +- .../bailout-retry/error.todo-syntax.js | 2 +- ...ror.untransformed-fire-reference.expect.md | 2 +- .../error.untransformed-fire-reference.js | 2 +- .../bailout-retry/error.use-no-memo.expect.md | 2 +- .../bailout-retry/error.use-no-memo.js | 2 +- .../infer-deps-on-retry.expect.md | 4 +- .../bailout-retry/infer-deps-on-retry.js | 2 +- ...-fire-todo-syntax-shouldnt-throw.expect.md | 4 +- .../no-fire-todo-syntax-shouldnt-throw.js | 2 +- ...ailout-validate-conditional-hook.expect.md | 4 +- .../bailout-validate-conditional-hook.js | 2 +- ...ro-dont-add-hook-guards-on-retry.expect.md | 2 +- .../repro-dont-add-hook-guards-on-retry.js | 2 +- ...suppression-skips-all-components.expect.md | 4 +- ...eslint-suppression-skips-all-components.js | 2 +- ...ule-scope-usememo-function-scope.expect.md | 4 +- ...emo-module-scope-usememo-function-scope.js | 2 +- compiler/packages/snap/src/compiler.ts | 110 +------------- 182 files changed, 350 insertions(+), 454 deletions(-) diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt index 5633cf0b0f..0f35215e86 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt @@ -1,5 +1,5 @@ import { c as _c } from "react/compiler-runtime"; //  -        @compilationMode(all) +        @compilationMode:"all" function nonReactFn() {   const $ = _c(1);   let t0; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt index b50c37fc4e..563a566a06 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function nonReactFn() {   return {}; } \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 48bb0eed4e..296f45a277 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -92,7 +92,7 @@ function useFoo(propVal: {+baz: number}) { }, { name: 'compilationMode-infer', - input: `// @compilationMode(infer) + input: `// @compilationMode:"infer" function nonReactFn() { return {}; } @@ -101,7 +101,7 @@ function nonReactFn() { }, { name: 'compilationMode-all', - input: `// @compilationMode(all) + input: `// @compilationMode:"all" function nonReactFn() { return {}; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 0adbf3077c..c732e16410 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -98,7 +98,7 @@ export type PluginOptions = { * provided rules will skip compilation. To disable this feature (never bailout of compilation * even if the default ESLint is suppressed), pass an empty array. */ - eslintSuppressionRules?: Array | null | undefined; + eslintSuppressionRules: Array | null | undefined; flowSuppressions: boolean; /* @@ -106,7 +106,7 @@ export type PluginOptions = { */ ignoreUseNoForget: boolean; - sources?: Array | ((filename: string) => boolean) | null; + sources: Array | ((filename: string) => boolean) | null; /** * The compiler has customized support for react-native-reanimated, intended as a temporary workaround. diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts index e221481724..b4484331de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts @@ -10,7 +10,6 @@ import {CompilerError} from '../CompilerError'; import { CompilationMode, defaultOptions, - PanicThresholdOptions, parsePluginOptions, PluginOptions, } from '../Entrypoint'; @@ -19,14 +18,24 @@ import { EnvironmentConfigSchema, PartialEnvironmentConfig, } from '../HIR/Environment'; +import {Err, Ok, Result} from './Result'; +import {hasOwnProperty} from './utils'; + +function tryParseTestPragmaValue(val: string): Result { + try { + let parsedVal: unknown; + const stringMatch = /^"([^"]*)"$/.exec(val); + if (stringMatch && stringMatch.length > 1) { + parsedVal = stringMatch[1]; + } else { + parsedVal = JSON.parse(val); + } + return Ok(parsedVal); + } catch (e) { + return Err(e); + } +} -/** - * For test fixtures and playground only. - * - * Pragmas are straightforward to parse for boolean options (`:true` and - * `:false`). These are 'enabled' config values for non-boolean configs (i.e. - * what is used when parsing `:true`). - */ const testComplexConfigDefaults: PartialEnvironmentConfig = { validateNoCapitalizedCalls: [], enableChangeDetectionForDebugging: { @@ -84,34 +93,37 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = { }, ], }; - /** * For snap test fixtures and playground only. */ function parseConfigPragmaEnvironmentForTest( pragma: string, ): EnvironmentConfig { - const maybeConfig: any = {}; - // Get the defaults to programmatically check for boolean properties - const defaultConfig = EnvironmentConfigSchema.parse({}); + const maybeConfig: Partial> = {}; for (const token of pragma.split(' ')) { if (!token.startsWith('@')) { continue; } const keyVal = token.slice(1); - let [key, val = undefined] = keyVal.split(':'); + const valIdx = keyVal.indexOf(':'); + const key = valIdx === -1 ? keyVal : keyVal.slice(0, valIdx); + const val = valIdx === -1 ? undefined : keyVal.slice(valIdx + 1); const isSet = val === undefined || val === 'true'; - - if (isSet && key in testComplexConfigDefaults) { - maybeConfig[key] = - testComplexConfigDefaults[key as keyof PartialEnvironmentConfig]; + if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) { continue; } - if (key === 'customMacros' && val) { - const valSplit = val.split('.'); - if (valSplit.length > 0) { + if (isSet && key in testComplexConfigDefaults) { + maybeConfig[key] = testComplexConfigDefaults[key]; + } else if (isSet) { + maybeConfig[key] = true; + } else if (val === 'false') { + maybeConfig[key] = false; + } else if (val) { + const parsedVal = tryParseTestPragmaValue(val).unwrap(); + if (key === 'customMacros' && typeof parsedVal === 'string') { + const valSplit = parsedVal.split('.'); const props = []; for (const elt of valSplit.slice(1)) { if (elt === '*') { @@ -121,21 +133,9 @@ function parseConfigPragmaEnvironmentForTest( } } maybeConfig[key] = [[valSplit[0], props]]; + continue; } - continue; - } - - if ( - key !== 'enableResetCacheOnSourceFileChanges' && - typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean' - ) { - // skip parsing non-boolean properties - continue; - } - if (val === undefined || val === 'true') { - maybeConfig[key] = true; - } else { - maybeConfig[key] = false; + maybeConfig[key] = parsedVal; } } const config = EnvironmentConfigSchema.safeParse(maybeConfig); @@ -156,6 +156,13 @@ function parseConfigPragmaEnvironmentForTest( suggestions: null, }); } + +const testComplexPluginOptionDefaults: Partial = { + gating: { + source: 'ReactForgetFeatureFlag', + importSpecifierName: 'isForgetEnabled_Fixtures', + }, +}; export function parseConfigPragmaForTests( pragma: string, defaults: { @@ -163,44 +170,41 @@ export function parseConfigPragmaForTests( }, ): PluginOptions { const environment = parseConfigPragmaEnvironmentForTest(pragma); - let compilationMode: CompilationMode = defaults.compilationMode; - let panicThreshold: PanicThresholdOptions = 'all_errors'; - let noEmit: boolean = defaultOptions.noEmit; + const options: Record = { + ...defaultOptions, + panicThreshold: 'all_errors', + compilationMode: defaults.compilationMode, + environment, + }; for (const token of pragma.split(' ')) { if (!token.startsWith('@')) { continue; } - switch (token) { - case '@compilationMode(annotation)': { - compilationMode = 'annotation'; - break; - } - case '@compilationMode(infer)': { - compilationMode = 'infer'; - break; - } - case '@compilationMode(all)': { - compilationMode = 'all'; - break; - } - case '@compilationMode(syntax)': { - compilationMode = 'syntax'; - break; - } - case '@panicThreshold(none)': { - panicThreshold = 'none'; - break; - } - case '@noEmit': { - noEmit = true; - break; + const keyVal = token.slice(1); + const idx = keyVal.indexOf(':'); + const key = idx === -1 ? keyVal : keyVal.slice(0, idx); + const val = idx === -1 ? undefined : keyVal.slice(idx + 1); + if (!hasOwnProperty(defaultOptions, key)) { + continue; + } + const isSet = val === undefined || val === 'true'; + if (isSet && key in testComplexPluginOptionDefaults) { + options[key] = testComplexPluginOptionDefaults[key]; + } else if (isSet) { + options[key] = true; + } else if (val === 'false') { + options[key] = false; + } else if (val != null) { + const parsedVal = tryParseTestPragmaValue(val).unwrap(); + if (key === 'target' && parsedVal === 'donotuse_meta_internal') { + options[key] = { + kind: parsedVal, + runtimeModule: 'react', + }; + } else { + options[key] = parsedVal; } } } - return parsePluginOptions({ - environment, - compilationMode, - panicThreshold, - noEmit, - }); + return parsePluginOptions(options); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md index 244e70bbba..d50d9d58cc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" const Test = () =>
; export const FIXTURE_ENTRYPOINT = { @@ -15,7 +15,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" const Test = () => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js index d2b29b1889..03195fd7d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" const Test = () =>
; export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md index 11abaa66ee..093a88b1de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" class Component { _renderMessage = () => { const Message = () => { @@ -22,7 +22,7 @@ class Component { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" class Component { _renderMessage = () => { const Message = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js index 90cba5b265..0786c82d6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" class Component { _renderMessage = () => { const Message = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md index 8c2f0b94ef..edabebecfc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" function Bar(props) { 'use forget'; @@ -24,7 +24,7 @@ function Foo(props) { ```javascript import { shouldInstrument, useRenderCounter } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode:"annotation" function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js index 2aef527e6b..cc5e471054 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js @@ -1,4 +1,4 @@ -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md index 12184dafbb..15cba5b29a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default component Foo(bar: number) { return ; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js index d29777d4f0..04d419c7ce 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js @@ -1,4 +1,4 @@ -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default component Foo(bar: number) { return ; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md index a2df134b2a..243c415467 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" import {identity} from 'shared-runtime'; @@ -35,7 +35,7 @@ import { shouldInstrument as _shouldInstrument3, useRenderCounter, } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode:"annotation" import { identity } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js index 88ffefe220..58729d6bee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js @@ -1,4 +1,4 @@ -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" import {identity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index 75da05a26e..d74ebd119c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @eslintSuppressionRules(my-app/react-rule) +// @eslintSuppressionRules:["my-app","react-rule"] /* eslint-disable my-app/react-rule */ function lowercasecomponent() { @@ -19,7 +19,7 @@ function lowercasecomponent() { ## Error ``` - 1 | // @eslintSuppressionRules(my-app/react-rule) + 1 | // @eslintSuppressionRules:["my-app","react-rule"] 2 | > 3 | /* eslint-disable my-app/react-rule */ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. eslint-disable my-app/react-rule (3:3) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js index 96d08827d5..331e551596 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js @@ -1,4 +1,4 @@ -// @eslintSuppressionRules(my-app/react-rule) +// @eslintSuppressionRules:["my-app","react-rule"] /* eslint-disable my-app/react-rule */ function lowercasecomponent() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md index 100dafaf38..6b63344a76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component({ref}) { const value = ref.current; return
{value}
; @@ -14,7 +14,7 @@ function Component({ref}) { ## Error ``` - 1 | // @validateRefAccessDuringRender @compilationMode(infer) + 1 | // @validateRefAccessDuringRender @compilationMode:"infer" 2 | function Component({ref}) { > 3 | const value = ref.current; | ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js index c5276b79b2..81c58cf252 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js @@ -1,4 +1,4 @@ -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component({ref}) { const value = ref.current; return
{value}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md index 4d8c4c735c..c49aea8740 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const value = props.ref.current; return
{value}
; @@ -14,7 +14,7 @@ function Component(props) { ## Error ``` - 1 | // @validateRefAccessDuringRender @compilationMode(infer) + 1 | // @validateRefAccessDuringRender @compilationMode:"infer" 2 | function Component(props) { > 3 | const value = props.ref.current; | ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js index c31a081366..abb1ff86fb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js @@ -1,4 +1,4 @@ -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const value = props.ref.current; return
{value}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md index c00aaca4fe..5038379283 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const ref = props.ref; ref.current = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js index b939ea540b..32997b8978 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js @@ -1,4 +1,4 @@ -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const ref = props.ref; ref.current = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md index 1d88b8d50c..2b9092213b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js index e23016cc49..d67232371d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md index a4a885d94b..68b275e34c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateBlocklistedImports(DangerousImport) +// @validateBlocklistedImports:["DangerousImport"] import {foo} from 'DangerousImport'; import {useIdentity} from 'shared-runtime'; @@ -17,7 +17,7 @@ function useHook() { ## Error ``` - 1 | // @validateBlocklistedImports(DangerousImport) + 1 | // @validateBlocklistedImports:["DangerousImport"] > 2 | import {foo} from 'DangerousImport'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Bailing out due to blocklisted import. Import from module DangerousImport (2:2) 3 | import {useIdentity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts index fcde4a1922..d33c175bcf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts @@ -1,4 +1,4 @@ -// @validateBlocklistedImports(DangerousImport) +// @validateBlocklistedImports:["DangerousImport"] import {foo} from 'DangerousImport'; import {useIdentity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md index 14bc703149..df2f4b01a9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; @@ -43,7 +43,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { useEffect, useMemo, useState } from "react"; import { ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js index 0bc38808b9..f1d96b5635 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md index 1af628811b..56a7289ae8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) @enableResetCacheOnSourceFileChanges +// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; @@ -46,7 +46,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableResetCacheOnSourceFileChanges +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableResetCacheOnSourceFileChanges import { useEffect, useMemo, useState } from "react"; import { ValidateMemoization } from "shared-runtime"; @@ -63,12 +63,12 @@ function unsafeUpdateConst() { function Component() { const $ = _c(3); if ( - $[0] !== "8d7015668f857996c3d895a7a90e3e16b8a791d5b9cd13f2c76e1c254aeedebb" + $[0] !== "a585d27423c1181e7b6305ff909458183d284658c3c3d2e3764e1128be302fd7" ) { for (let $i = 0; $i < 3; $i += 1) { $[$i] = Symbol.for("react.memo_cache_sentinel"); } - $[0] = "8d7015668f857996c3d895a7a90e3e16b8a791d5b9cd13f2c76e1c254aeedebb"; + $[0] = "a585d27423c1181e7b6305ff909458183d284658c3c3d2e3764e1128be302fd7"; } useState(_temp); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js index cc5c1a4abf..9130f13c15 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) @enableResetCacheOnSourceFileChanges +// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md index 6256e5ec90..5eaa593fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitInstrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode:"annotation" @gating function Bar(props) { 'use forget'; @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { shouldInstrument, useRenderCounter } from "react-compiler-runtime"; import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode:"annotation" @gating const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js index 6730630ac3..425b99da8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js @@ -1,4 +1,4 @@ -// @enableEmitInstrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode:"annotation" @gating function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md index ae7896e7a5..fbe3c554c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js index 1ec9a902b2..369dc3bbd0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md index 4c368a8254..8665addb8a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; @@ -35,7 +35,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js index 5b7f7c81b7..af2f098130 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md index c3e2e4a042..9661fe3d2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export function Bar(props) { 'use forget'; return
{props.bar}
; @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" export const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js index 5bcf9ac67d..e07144b160 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md index 8ccce56713..0cd67fd1d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" function Bar(props) { 'use forget'; return
{props.bar}
; @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js index 3bb92654d9..bc45933af3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md index aff21ee0bd..1106722259 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(infer) +// @gating @compilationMode:"infer" import React from 'react'; export default React.forwardRef(function notNamedLikeAComponent(props) { return
; @@ -14,7 +14,7 @@ export default React.forwardRef(function notNamedLikeAComponent(props) { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(infer) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"infer" import React from "react"; export default React.forwardRef( isForgetEnabled_Fixtures() diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js index 79acdd821a..518c8eeee3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(infer) +// @gating @compilationMode:"infer" import React from 'react'; export default React.forwardRef(function notNamedLikeAComponent(props) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md index f75514e8a8..d23e808983 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default hook useFoo(bar: number) { return [bar]; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js index 217fbe1636..13db3325ce 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js @@ -1,4 +1,4 @@ -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default hook useFoo(bar: number) { return [bar]; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md index dbc90978d5..59875c8d2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @customMacros(idx.*.b) +// @customMacros:"idx.*.b" function Component(props) { // outlined @@ -31,7 +31,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @customMacros(idx.*.b) +import { c as _c } from "react/compiler-runtime"; // @customMacros:"idx.*.b" function Component(props) { const $ = _c(16); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js index 4b944ddcca..5a362bbbe7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js @@ -1,4 +1,4 @@ -// @customMacros(idx.*.b) +// @customMacros:"idx.*.b" function Component(props) { // outlined diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md index f1bced727b..9c748d84be 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @customMacros(idx.a) +// @customMacros:"idx.a" function Component(props) { // outlined @@ -25,7 +25,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @customMacros(idx.a) +import { c as _c } from "react/compiler-runtime"; // @customMacros:"idx.a" function Component(props) { const $ = _c(10); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js index f5b034b91d..1b2cadb5d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js @@ -1,4 +1,4 @@ -// @customMacros(idx.a) +// @customMacros:"idx.a" function Component(props) { // outlined diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md index a0fafc56c8..880cdcdb21 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @customMacros(idx) +// @customMacros:"idx" import idx from 'idx'; function Component(props) { @@ -21,7 +21,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @customMacros(idx) +import { c as _c } from "react/compiler-runtime"; // @customMacros:"idx" function Component(props) { var _ref2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js index 7d16c8d2b7..8cdf205ddd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js @@ -1,4 +1,4 @@ -// @customMacros(idx) +// @customMacros:"idx" import idx from 'idx'; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md index 4263d8db8d..129d3a72e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useNoAlias} from 'shared-runtime'; // This should be compiled by Forget @@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { useNoAlias } from "shared-runtime"; // This should be compiled by Forget diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js index e354c67bb2..a03aa10aa8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useNoAlias} from 'shared-runtime'; // This should be compiled by Forget diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md index d0999fca0c..9b35a83801 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Takes multiple parameters - not a component! function Component(foo, bar) { return
; @@ -18,7 +18,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Takes multiple parameters - not a component! function Component(foo, bar) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js index 9b2cf36a19..442c367fde 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Takes multiple parameters - not a component! function Component(foo, bar) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md index cd977ae834..8cbe8da62f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import useMyEffect from 'useEffectWrapper'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js index 8061b44a3d..992cd828ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import useMyEffect from 'useEffectWrapper'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md index e36e9b3c0e..8feec25376 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import {useEffect} from 'react'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js index 813c3b6c3d..106a3c7352 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import {useEffect} from 'react'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md index ee17fb048e..eeec00995b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js index a011e3bf14..dac029e0ca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md index 71ecefeeea..416ede4556 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import React from 'react'; function NonReactiveDepInEffect() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js index 237535fe5b..b1b0e8d2fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import React from 'react'; function NonReactiveDepInEffect() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md index 575fd2101f..a396b9c3ca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useSpecialEffect} from 'shared-runtime'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js index d3c83c2562..2c53d1a21b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useSpecialEffect} from 'shared-runtime'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md index 52b99a393e..c3413f9fd0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({propVal}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js index 53eeda5050..d00ce2ac98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({propVal}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md index 7099388378..701f54b663 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; import {print} from 'shared-runtime'; @@ -20,7 +20,7 @@ function Component({foo}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useEffect } from "react"; import { print } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js index fe00af3922..f1b3de4f7f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index d0532a495a..05ef40c150 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -19,7 +19,7 @@ function Component({arrRef}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useEffect, useRef } from "react"; import { print } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js index ff2cda6b89..f497d7e595 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index 5da3ceca22..fa1df3ef88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({foo}) { @@ -17,7 +17,7 @@ function Component({foo}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useEffect } from "react"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js index 806ca5322f..2e2eb7bc08 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({foo}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md index 8038a8d071..89a89c41e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" React.memo(props => { return
; }); @@ -12,7 +12,7 @@ React.memo(props => { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" React.memo((props) => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js index a8da0532df..d9e1eaf31a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" React.memo(props => { return
; }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md index 92bbaf7c30..c183bf949c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" const Component = props => { return
; }; @@ -12,7 +12,7 @@ const Component = props => { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" const Component = (props) => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js index 6dff8028b9..c12f61d18f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" const Component = props => { return
; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md index 24a7050c68..49886e0ac3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" const Component = function ComponentName(props) { return ; @@ -13,7 +13,7 @@ const Component = function ComponentName(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" const Component = function ComponentName(props) { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js index 7fe4c9c0cc..deac307a3b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" const Component = function ComponentName(props) { return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md index 011006b2aa..ca1d6e4336 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" React.forwardRef(props => { return
; }); @@ -12,7 +12,7 @@ React.forwardRef(props => { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" React.forwardRef((props) => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js index c0abf753c2..f2b458df8d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" React.forwardRef(props => { return
; }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md index 51c9a14abf..d92f1a6c45 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const [state, _] = useState(null); return [state]; @@ -13,7 +13,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function Component(props) { const $ = _c(2); const [state] = useState(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js index 791542cb34..57c03cd222 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const [state, _] = useState(null); return [state]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md index 96cae6ee6a..ed175c61ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { return
; } @@ -12,7 +12,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function Component(props) { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js index bb552b5f2a..73607ce519 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { return
; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md index c19248a0c0..5f3da3fd8e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Foo({}, ref) { return
; @@ -18,7 +18,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function Foo(t0, ref) { const $ = _c(2); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js index aaf9492528..cefd53b123 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Foo({}, ref) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md index d1ed626a97..a94055469a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function useStateValue(props) { const [state, _] = useState(null); return [state]; @@ -13,7 +13,7 @@ function useStateValue(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function useStateValue(props) { const $ = _c(2); const [state] = useState(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js index ed90449f36..7191fe6cb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function useStateValue(props) { const [state, _] = useState(null); return [state]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md index dfc6fdbc2e..3f4c0a0173 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function useDiv(props) { return
; } @@ -12,7 +12,7 @@ function useDiv(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function useDiv(props) { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js index 3453fc8286..2fef05df39 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function useDiv(props) { return
; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md index 4c89f59919..963bbc7d3e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {Stringify} from 'shared-runtime'; @@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx index c8e396b057..cb9d22a9f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md index b5710f5840..1334ab1f0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useIdentity, identity} from 'shared-runtime'; function Component(fakeProps: number) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import { useIdentity, identity } from "shared-runtime"; function Component(fakeProps: number) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts index 81ec3e34e3..e628f9c685 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useIdentity, identity} from 'shared-runtime'; function Component(fakeProps: number) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md index adb9a1da40..28672a1da1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const result = f(props); function helper() { @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const result = f(props); function helper() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js index 9d9e070ca9..11a21ab56e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const result = f(props); function helper() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md index 70782b6aab..27d7ee1c1e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const ignore = ; return {foo: f(props)}; @@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const ignore = ; return { foo: f(props) }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js index f48e2b1e15..950988ef63 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const ignore = ; return {foo: f(props)}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md index 0637082e50..d4d4c1bc0e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This component is skipped bc it doesn't call any hooks or // use JSX: function Component(props) { @@ -14,7 +14,7 @@ function Component(props) { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This component is skipped bc it doesn't call any hooks or // use JSX: function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js index 5f6aa674b2..c1396aca94 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // This component is skipped bc it doesn't call any hooks or // use JSX: function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md index 2e052ecf33..9f4d85de70 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements import {identity} from 'shared-runtime'; function Component(props) { @@ -25,7 +25,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoJSXInTryStatements +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoJSXInTryStatements import { identity } from "shared-runtime"; function Component(props) { @@ -65,8 +65,8 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":11,"column":11,"index":214},"end":{"line":11,"column":32,"index":235},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":83},"end":{"line":17,"column":1,"index":290},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":17,"column":1,"index":298},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js index 2e5a3618b4..3cf5cc9b8a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js @@ -1,4 +1,4 @@ -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements import {identity} from 'shared-runtime'; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md index 702d317a4b..9fe89f25ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements function Component(props) { let el; try { @@ -18,7 +18,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoJSXInTryStatements +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoJSXInTryStatements function Component(props) { const $ = _c(1); let el; @@ -42,8 +42,8 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":5,"column":9,"index":96},"end":{"line":5,"column":16,"index":103},"filename":"invalid-jsx-in-try-with-catch.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":41},"end":{"line":10,"column":1,"index":152},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":49},"end":{"line":10,"column":1,"index":160},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js index 815fb4ae9e..d01a93bf5a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js @@ -1,4 +1,4 @@ -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements function Component(props) { let el; try { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index d220617dc1..e116fb4fd9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { @@ -24,7 +24,7 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoSetStateInPassiveEffects +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInPassiveEffects import { useEffect, useState } from "react"; function Component() { @@ -65,8 +65,8 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":264},"end":{"line":13,"column":5,"index":265},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":16,"column":1,"index":292},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":272},"end":{"line":13,"column":5,"index":273},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":99},"end":{"line":16,"column":1,"index":300},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js index b03c08609a..bbf976f0bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js @@ -1,4 +1,4 @@ -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index 667f57b7cc..202071455b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { @@ -18,7 +18,7 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoSetStateInPassiveEffects +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInPassiveEffects import { useEffect, useState } from "react"; function Component() { @@ -45,8 +45,8 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":179},"end":{"line":7,"column":12,"index":187},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":10,"column":1,"index":224},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":187},"end":{"line":7,"column":12,"index":195},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":99},"end":{"line":10,"column":1,"index":232},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js index e806b359c6..e526ad82e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js @@ -1,4 +1,4 @@ -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md index 99796806bc..b6583dbc9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger +// @loggerTestOnly import {createContext, use, useState} from 'react'; import { Stringify, @@ -56,7 +56,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly import { createContext, use, useState } from "react"; import { Stringify, @@ -129,8 +129,8 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileSuccess","fnLoc":{"start":{"line":10,"column":0,"index":159},"end":{"line":33,"column":1,"index":903},"filename":"log-pruned-memoization.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":2,"prunedMemoValues":3} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":37,"column":0,"index":939},"end":{"line":43,"column":1,"index":1037},"filename":"log-pruned-memoization.ts"},"fnName":"Wrapper","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":10,"column":0,"index":167},"end":{"line":33,"column":1,"index":911},"filename":"log-pruned-memoization.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":2,"prunedMemoValues":3} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":37,"column":0,"index":947},"end":{"line":43,"column":1,"index":1045},"filename":"log-pruned-memoization.ts"},"fnName":"Wrapper","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js index d0097b4eb3..42c39d1e71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js @@ -1,4 +1,4 @@ -// @logger +// @loggerTestOnly import {createContext, use, useState} from 'react'; import { Stringify, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md index 55ffec877d..08a3ccb87f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros:"cx" import {identity} from 'shared-runtime'; const DARK = 'dark'; @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros:"cx" import { identity } from "shared-runtime"; const DARK = "dark"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js index ac6a475e0c..c72f693754 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros:"cx" import {identity} from 'shared-runtime'; const DARK = 'dark'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md index aeae54128b..c99b05a113 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) import {identity} from 'shared-runtime'; const DARK = 'dark'; @@ -49,7 +49,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) import { identity } from "shared-runtime"; const DARK = "dark"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js index fe7abb7cdf..4254c5a442 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) import {identity} from 'shared-runtime'; const DARK = 'dark'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md index 3224997b40..13e67f0e49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" import {useHook} from 'shared-runtime'; function InvalidComponent(props) { @@ -21,7 +21,7 @@ function ValidComponent(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @panicThreshold(none) +import { c as _c } from "react/compiler-runtime"; // @panicThreshold:"none" import { useHook } from "shared-runtime"; function InvalidComponent(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js index 6a3d52c864..80f9bdd109 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js @@ -1,4 +1,4 @@ -// @panicThreshold(none) +// @panicThreshold:"none" import {useHook} from 'shared-runtime'; function InvalidComponent(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md index 3297892ea2..4c5da20170 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useMemo} from 'react'; import {ValidateMemoization} from 'shared-runtime'; @@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { useMemo } from "react"; import { ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js index 4c2d322ad3..5882e3cc6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useMemo} from 'react'; import {ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md index 6477654d3d..b19a06519f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire component Foo(useDynamicHook) { useDynamicHook(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js index d0313aa42f..54b1818213 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js @@ -1,4 +1,4 @@ -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire component Foo(useDynamicHook) { useDynamicHook(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md index ae9edc478e..a153a5cf74 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" import {useNoAlias} from 'shared-runtime'; const cond = true; @@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" import { useNoAlias } from "shared-runtime"; const cond = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js index ffed061d99..fc96e1435f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js @@ -1,4 +1,4 @@ -// @panicThreshold(none) +// @panicThreshold:"none" import {useNoAlias} from 'shared-runtime'; const cond = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md index 7394c17b9c..2d1860bc3b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const f = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js index 240926d979..bfe82523a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const f = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md index 4d1e7e530c..7403afcaf7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const x = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js index 54d4a89d46..779cad41f9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const x = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md index 4356c33c9a..82b6998634 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Regression test for some internal code. // This shows how the "callback rule" is more relaxed, // and doesn't kick in unless we're confident we're in @@ -20,7 +20,7 @@ function makeListener(instance) { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Regression test for some internal code. // This shows how the "callback rule" is more relaxed, // and doesn't kick in unless we're confident we're in diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js index f83f535928..360593a570 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Regression test for some internal code. // This shows how the "callback rule" is more relaxed, // and doesn't kick in unless we're confident we're in diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md index 9a6ca0d249..f5bd1a042a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can call hooks. function createHook() { return function useHook() { @@ -16,7 +16,7 @@ function createHook() { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can call hooks. function createHook() { return function useHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js index f27e6bcf5f..fcdf80d224 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can call hooks. function createHook() { return function useHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md index ea8869bacd..11330a9c4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can use hooks. function createHook() { return function useHookWithHook() { @@ -15,7 +15,7 @@ function createHook() { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can use hooks. function createHook() { return function useHookWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js index ce6244346f..3f3a4c0576 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can use hooks. function createHook() { return function useHookWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md index dfb4fa6df7..c0f5ad939d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because components can use hooks. function createComponentWithHook() { return function ComponentWithHook() { @@ -15,7 +15,7 @@ function createComponentWithHook() { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because components can use hooks. function createComponentWithHook() { return function ComponentWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js index 2aced0739c..31a5d85356 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because components can use hooks. function createComponentWithHook() { return function ComponentWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md index 1c676d9825..ce42d44a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This is valid because "use"-prefixed functions called in // unnamed function arguments are not assumed to be hooks. unknownFunction(function (foo, bar) { @@ -16,7 +16,7 @@ unknownFunction(function (foo, bar) { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This is valid because "use"-prefixed functions called in // unnamed function arguments are not assumed to be hooks. unknownFunction(function (foo, bar) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js index a480e74a83..b7744c5ff7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // This is valid because "use"-prefixed functions called in // unnamed function arguments are not assumed to be hooks. unknownFunction(function (foo, bar) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md index 6103aa5094..bed1b9e601 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @panicThreshold(none) @compilationMode(annotation) +// @gating @panicThreshold:"none" @compilationMode:"annotation" let someGlobal = 'joe'; function Component() { @@ -21,7 +21,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @gating @panicThreshold(none) @compilationMode(annotation) +// @gating @panicThreshold:"none" @compilationMode:"annotation" let someGlobal = "joe"; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js index 1a350a1df7..8d0264cd17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js @@ -1,4 +1,4 @@ -// @gating @panicThreshold(none) @compilationMode(annotation) +// @gating @panicThreshold:"none" @compilationMode:"annotation" let someGlobal = 'joe'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md index abbf9aff13..36b91bcf2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @panicThreshold(none) @compilationMode(infer) +// @gating @panicThreshold:"none" @compilationMode:"infer" let someGlobal = 'joe'; function Component() { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @gating @panicThreshold(none) @compilationMode(infer) +// @gating @panicThreshold:"none" @compilationMode:"infer" let someGlobal = "joe"; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js index dedabda9d6..1de4cb4490 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js @@ -1,4 +1,4 @@ -// @gating @panicThreshold(none) @compilationMode(infer) +// @gating @panicThreshold:"none" @compilationMode:"infer" let someGlobal = 'joe'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md index 836d0fe7d9..af8103b7ae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { let Component; if (props.cond) { @@ -18,7 +18,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(3); let Component; @@ -50,9 +50,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":10,"index":194},"end":{"line":9,"column":19,"index":203},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":5,"column":16,"index":116},"end":{"line":5,"column":33,"index":133},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":10,"column":1,"index":209},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":10,"column":1,"index":217},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js index 580d330728..6ff7ff321c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { let Component; if (props.cond) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md index a222bd44ca..7720863da3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = createComponent(); return ; @@ -13,7 +13,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(1); let t0; @@ -32,9 +32,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":112},"end":{"line":4,"column":19,"index":121},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":37,"index":100},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":127},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":135},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js index ebbb2b239d..209cc83d65 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = createComponent(); return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md index 41339bd8c4..8d218bf24b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { function Component() { return
; @@ -15,7 +15,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(1); let t0; @@ -37,9 +37,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":6,"column":10,"index":122},"end":{"line":6,"column":19,"index":131},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":2,"index":65},"end":{"line":5,"column":3,"index":111},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":7,"column":1,"index":137},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":7,"column":1,"index":145},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js index 1ce24193d2..e48712dbf7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { function Component() { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md index c53cf894cd..e3bc7a5eb5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = props.foo.bar(); return ; @@ -13,7 +13,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(4); let t0; @@ -41,9 +41,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":110},"end":{"line":4,"column":19,"index":119},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":35,"index":98},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":125},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":133},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js index 01c3fb85f9..7f43ffacbd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = props.foo.bar(); return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md index d45f263adb..02e9f4f4a4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = new ComponentFactory(); return ; @@ -13,7 +13,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(1); let t0; @@ -32,9 +32,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":117},"end":{"line":4,"column":19,"index":126},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":42,"index":105},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":132},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":140},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js index 4d5ba62d1e..a735e076d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = new ComponentFactory(); return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md index acf34a474c..6c81defa1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md @@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react"; // @target="donotuse_meta_internal" +import { c as _c } from "react/compiler-runtime"; // @target="donotuse_meta_internal" function Component() { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md index b4cf41e369..b27b786d57 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md @@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react-compiler-runtime"; // @target="18" +import { c as _c } from "react/compiler-runtime"; // @target="18" function Component() { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md index 77bdd31275..b11be5668d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateNoCapitalizedCalls @enableFire @panicThreshold(none) +// @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" import {fire} from 'react'; const CapitalizedCall = require('shared-runtime').sum; @@ -24,7 +24,7 @@ function Component({prop1, bar}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" import { fire } from "react"; const CapitalizedCall = require("shared-runtime").sum; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js index 679945eaab..bd9715e91a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js @@ -1,4 +1,4 @@ -// @validateNoCapitalizedCalls @enableFire @panicThreshold(none) +// @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" import {fire} from 'react'; const CapitalizedCall = require('shared-runtime').sum; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md index 30bd6d42e5..99774bdd3e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {useRef} from 'react'; function Component({props, bar}) { @@ -26,7 +26,7 @@ function Component({props, bar}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { useRef } from "react"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js index 5312e5707c..be89a6d3b9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {useRef} from 'react'; function Component({props, bar}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md index 6477a01126..1a22df2941 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none) +// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold:"none" import {fire} from 'react'; import {sum} from 'shared-runtime'; @@ -24,7 +24,7 @@ function Component({prop1, bar}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold:"none" import { fire } from "react"; import { sum } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js index c3bb8b4216..4ba2de0eb1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none) +// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold:"none" import {fire} from 'react'; import {sum} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md index e6ce051f10..0f1d745537 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; function Component({prop1}) { @@ -20,7 +20,7 @@ function Component({prop1}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { fire } from "react"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js index b0cfd4fe39..6b250a1692 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; function Component({prop1}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md index 79f5a2986d..b55526e921 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @enableFire @panicThreshold(none) +// @flow @enableFire @panicThreshold:"none" import {fire} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js index f649fe5902..1dcb5b5c47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js @@ -1,4 +1,4 @@ -// @flow @enableFire @panicThreshold(none) +// @flow @enableFire @panicThreshold:"none" import {fire} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index c5d7456d65..aa3d989296 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js index b1ee459177..52c224e321 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index ddcc86ff00..0141ffb8ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; console.log(fire == null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js index 25a60a9716..afeead0812 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; console.log(fire == null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index 84a27b43b8..275012351c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js index 92e1ff211a..685309a250 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md index 42c800f8e5..36ed7a3d36 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useRef} from 'react'; import {useSpecialEffect} from 'shared-runtime'; @@ -25,7 +25,7 @@ function useFoo({cond}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useRef } from "react"; import { useSpecialEffect } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js index f3a57bb912..4f6b908b53 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useRef} from 'react'; import {useSpecialEffect} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md index 7ba4ee2811..06268ea854 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** @@ -43,7 +43,7 @@ function FireComponent(props) { ## Code ```javascript -import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { fire } from "react"; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js index 899fa33376..e11ab6b6de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md index dde2b692f4..2c81d693b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire, useEffect} from 'react'; import {Stringify} from 'shared-runtime'; @@ -29,7 +29,7 @@ function Component(props) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { fire, useEffect } from "react"; import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js index fa8c034bfb..602f121ad7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire, useEffect} from 'react'; import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md index c7ed50ceba..75c46e196e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire import {useEffect, fire} from 'react'; function Component(props, useDynamicHook) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js index 077982e8d4..5fc11c9a82 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js @@ -1,4 +1,4 @@ -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire import {useEffect, fire} from 'react'; function Component(props, useDynamicHook) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md index f0c6bce342..2a419a6415 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" // unclosed disable rule should affect all components /* eslint-disable react-hooks/rules-of-hooks */ @@ -20,7 +20,7 @@ function ValidComponent2(props) { ## Code ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" // unclosed disable rule should affect all components /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js index 121f100418..a3e2bab77e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js @@ -1,4 +1,4 @@ -// @panicThreshold(none) +// @panicThreshold:"none" // unclosed disable rule should affect all components /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md index 69c1b9bbbb..375a38664e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(all) +// @compilationMode:"all" 'use no memo'; function TestComponent({x}) { @@ -15,7 +15,7 @@ function TestComponent({x}) { ## Code ```javascript -// @compilationMode(all) +// @compilationMode:"all" "use no memo"; function TestComponent({ x }) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js index 9b314e1f99..21eec7ab2c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -1,4 +1,4 @@ -// @compilationMode(all) +// @compilationMode:"all" 'use no memo'; function TestComponent({x}) { diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index bc9578e0ee..0cadf30bf0 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -20,10 +20,6 @@ import type { } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils'; -import type { - Macro, - MacroMethod, -} from 'babel-plugin-react-compiler/src/HIR/Environment'; import * as HermesParser from 'hermes-parser'; import invariant from 'invariant'; import path from 'path'; @@ -47,52 +43,10 @@ function makePluginOptions( EffectEnum: typeof Effect, ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { - let gating = null; - let hookPattern: string | null = null; // TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false let validatePreserveExistingMemoizationGuarantees = false; - let customMacros: null | Array = null; - let validateBlocklistedImports = null; let target: CompilerReactTarget = '19'; - if (firstLine.includes('@gating')) { - gating = { - source: 'ReactForgetFeatureFlag', - importSpecifierName: 'isForgetEnabled_Fixtures', - }; - } - - const targetMatch = /@target="([^"]+)"/.exec(firstLine); - if (targetMatch) { - if (targetMatch[1] === 'donotuse_meta_internal') { - target = { - kind: targetMatch[1], - runtimeModule: 'react', - }; - } else { - // @ts-ignore - target = targetMatch[1]; - } - } - - let eslintSuppressionRules: Array | null = null; - const eslintSuppressionMatch = /@eslintSuppressionRules\(([^)]+)\)/.exec( - firstLine, - ); - if (eslintSuppressionMatch != null) { - eslintSuppressionRules = eslintSuppressionMatch[1].split('|'); - } - - let flowSuppressions: boolean = false; - if (firstLine.includes('@enableFlowSuppressions')) { - flowSuppressions = true; - } - - let ignoreUseNoForget: boolean = false; - if (firstLine.includes('@ignoreUseNoForget')) { - ignoreUseNoForget = true; - } - /** * Snap currently runs all fixtures without `validatePreserveExistingMemo` as * most fixtures are interested in compilation output, not whether the @@ -105,64 +59,9 @@ function makePluginOptions( validatePreserveExistingMemoizationGuarantees = true; } - const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine); - if ( - hookPatternMatch && - hookPatternMatch.length > 1 && - hookPatternMatch[1].trim().length > 0 - ) { - hookPattern = hookPatternMatch[1].trim(); - } else if (firstLine.includes('@hookPattern')) { - throw new Error( - 'Invalid @hookPattern:"..." pragma, must contain the prefix between balanced double quotes eg @hookPattern:"pattern"', - ); - } - - const customMacrosMatch = /@customMacros\(([^)]+)\)/.exec(firstLine); - if ( - customMacrosMatch && - customMacrosMatch.length > 1 && - customMacrosMatch[1].trim().length > 0 - ) { - const customMacrosStrs = customMacrosMatch[1] - .split(' ') - .map(s => s.trim()) - .filter(s => s.length > 0); - if (customMacrosStrs.length > 0) { - customMacros = []; - for (const customMacroStr of customMacrosStrs) { - const props: Array = []; - const customMacroSplit = customMacroStr.split('.'); - if (customMacroSplit.length > 0) { - for (const elt of customMacroSplit.slice(1)) { - if (elt === '*') { - props.push({type: 'wildcard'}); - } else if (elt.length > 0) { - props.push({type: 'name', name: elt}); - } - } - customMacros.push([customMacroSplit[0], props]); - } - } - } - } - - const validateBlocklistedImportsMatch = - /@validateBlocklistedImports\(([^)]+)\)/.exec(firstLine); - if ( - validateBlocklistedImportsMatch && - validateBlocklistedImportsMatch.length > 1 && - validateBlocklistedImportsMatch[1].trim().length > 0 - ) { - validateBlocklistedImports = validateBlocklistedImportsMatch[1] - .split(' ') - .map(s => s.trim()) - .filter(s => s.length > 0); - } - const logs: Array<{filename: string | null; event: LoggerEvent}> = []; const logger: Logger = { - logEvent: firstLine.includes('@logger') + logEvent: firstLine.includes('@loggerTestOnly') ? (filename, event) => { logs.push({filename, event}); } @@ -179,17 +78,10 @@ function makePluginOptions( EffectEnum, ValueKindEnum, }), - customMacros, assertValidMutableRanges: true, - hookPattern, validatePreserveExistingMemoizationGuarantees, - validateBlocklistedImports, }, logger, - gating, - eslintSuppressionRules, - flowSuppressions, - ignoreUseNoForget, enableReanimatedCheck: false, target, }; From 3cdf44994e2a5c8bdda889c9546f8e4001c4e7cf Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 11:27:18 -0400 Subject: [PATCH 373/422] [compiler][be] Make program traversal more readable React Compiler's program traversal logic is pretty lengthy and complex as we've added a lot of features piecemeal. `compileProgram` is 300+ lines long and has confusing control flow (defining helpers inline, invoking visitors, mutating-asts-while-iterating, mutating global `ALREADY_COMPILED` state). - Moved more stuff to `ProgramContext` - Separated `compileProgram` into a bunch of helpers Will come back and add more comments inline --- .../src/Entrypoint/Imports.ts | 62 +- .../src/Entrypoint/Program.ts | 560 ++++++++++-------- .../ValidateNoUntransformedReferences.ts | 6 +- 3 files changed, 363 insertions(+), 265 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 704f696b3b..d5cac921e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -18,8 +18,9 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; -import {CompilerReactTarget} from './Options'; -import {getReactCompilerRuntimeModule} from './Program'; +import {LoggerEvent, PluginOptions} from './Options'; +import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( path: NodePath, @@ -52,32 +53,61 @@ export function validateRestrictedImports( } } +type ProgramContextOptions = { + program: NodePath; + suppressions: Array; + opts: PluginOptions; + filename: string | null; + code: string | null; +}; export class ProgramContext { - /* Program and environment context */ + /** + * Program and environment context + */ scope: BabelScope; + opts: PluginOptions; + filename: string | null; + code: string | null; reactRuntimeModule: string; - hookPattern: string | null; + suppressions: Array; + /* + * This is a hack to work around what seems to be a Babel bug. Babel doesn't + * consistently respect the `skip()` function to avoid revisiting a node within + * a pass, so we use this set to track nodes that we have compiled. + */ + alreadyCompiled: WeakSet | Set = new (WeakSet ?? Set)(); // known generated or referenced identifiers in the program knownReferencedNames: Set = new Set(); // generated imports imports: Map> = new Map(); - constructor( - program: NodePath, - reactRuntimeModule: CompilerReactTarget, - hookPattern: string | null, - ) { - this.hookPattern = hookPattern; + /** + * Metadata from compilation + */ + retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; + inferredEffectLocations: Set = new Set(); + + constructor({ + program, + suppressions, + opts, + filename, + code, + }: ProgramContextOptions) { this.scope = program.scope; - this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule); + this.opts = opts; + this.filename = filename; + this.code = code; + this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); + this.suppressions = suppressions; } isHookName(name: string): boolean { - if (this.hookPattern == null) { + if (this.opts.environment.hookPattern == null) { return isHookName(name); } else { - const match = new RegExp(this.hookPattern).exec(name); + const match = new RegExp(this.opts.environment.hookPattern).exec(name); return ( match != null && typeof match[1] === 'string' && isHookName(match[1]) ); @@ -179,6 +209,12 @@ export class ProgramContext { }); return Err(error); } + + logEvent(event: LoggerEvent): void { + if (this.opts.logger != null) { + this.opts.logger.logEvent(this.filename, event); + } + } } function getExistingImports( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 34347f10b8..58e3c7de01 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment'; +import {ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -43,17 +43,21 @@ export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); export function findDirectiveEnablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), + ) ?? null ); } export function findDirectiveDisablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_OUT_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_OUT_DIRECTIVES.has(directive.value.value), + ) ?? null ); } @@ -88,13 +92,16 @@ export type CompileResult = { function logError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - if (pass.opts.logger) { + if (context.opts.logger) { if (err instanceof CompilerError) { for (const detail of err.details) { - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, detail: detail.options, @@ -108,7 +115,7 @@ function logError( stringifiedError = err?.toString() ?? '[ null ]'; } - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'PipelineError', fnLoc, data: stringifiedError, @@ -118,13 +125,17 @@ function logError( } function handleError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - logError(err, pass, fnLoc); + logError(err, context, fnLoc); if ( - pass.opts.panicThreshold === 'all_errors' || - (pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) || + context.opts.panicThreshold === 'all_errors' || + (context.opts.panicThreshold === 'critical_errors' && + isCriticalError(err)) || isConfigError(err) // Always throws regardless of panic threshold ) { throw err; @@ -187,7 +198,6 @@ export function createNewFunctionNode( } } // Avoid visiting the new transformed version - ALREADY_COMPILED.add(transformedFn); return transformedFn; } @@ -239,13 +249,6 @@ function insertNewOutlinedFunctionNode( } } -/* - * This is a hack to work around what seems to be a Babel bug. Babel doesn't - * consistently respect the `skip()` function to avoid revisiting a node within - * a pass, so we use this set to track nodes that we have compiled. - */ -const ALREADY_COMPILED: WeakSet | Set = new (WeakSet ?? Set)(); - const DEFAULT_ESLINT_SUPPRESSIONS = [ 'react-hooks/exhaustive-deps', 'react-hooks/rules-of-hooks', @@ -268,41 +271,43 @@ function isFilePartOfSources( return false; } -export type CompileProgramResult = { +export type CompileProgramMetadata = { retryErrors: Array<{fn: BabelFn; error: CompilerError}>; inferredEffectLocations: Set; }; /** - * `compileProgram` is directly invoked by the react-compiler babel plugin, so - * exceptions thrown by this function will fail the babel build. - * - call `handleError` if your error is recoverable. - * Unless the error is a warning / info diagnostic, compilation of a function - * / entire file should also be skipped. - * - throw an exception if the error is fatal / not recoverable. - * Examples of this are invalid compiler configs or failure to codegen outlined - * functions *after* already emitting optimized components / hooks that invoke - * the outlined functions. + * Main entrypoint for React Compiler. + * + * @param program The Babel program node to compile + * @param pass Compiler configuration and context + * @returns Compilation results or null if compilation was skipped */ export function compileProgram( program: NodePath, pass: CompilerPass, -): CompileProgramResult | null { +): CompileProgramMetadata | null { + /** + * This is directly invoked by the react-compiler babel plugin, so exceptions + * thrown by this function will fail the babel build. + * - call `handleError` if your error is recoverable. + * Unless the error is a warning / info diagnostic, compilation of a function + * / entire file should also be skipped. + * - throw an exception if the error is fatal / not recoverable. + * Examples of this are invalid compiler configs or failure to codegen outlined + * functions *after* already emitting optimized components / hooks that invoke + * the outlined functions. + */ if (shouldSkipCompilation(program, pass)) { return null; } - - const environment = pass.opts.environment; - const restrictedImportsErr = validateRestrictedImports(program, environment); + const restrictedImportsErr = validateRestrictedImports( + program, + pass.opts.environment, + ); if (restrictedImportsErr) { handleError(restrictedImportsErr, pass, null); return null; } - - const programContext = new ProgramContext( - program, - pass.opts.target, - environment.hookPattern, - ); /* * Record lint errors and critical errors as depending on Forget's config, * we may still need to run Forget's analysis on every function (even if we @@ -313,16 +318,88 @@ export function compileProgram( pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions, ); - const queue: Array<{ - kind: 'original' | 'outlined'; - fn: BabelFn; - fnType: ReactFunctionType; - }> = []; + + const programContext = new ProgramContext({ + program: program, + opts: pass.opts, + filename: pass.filename, + code: pass.code, + suppressions, + }); + + const queue: Array = findFunctionsToCompile( + program, + pass, + programContext, + ); const compiledFns: Array = []; + while (queue.length !== 0) { + const current = queue.shift()!; + const compiled = processFn(current.fn, current.fnType, programContext); + + if (compiled != null) { + for (const outlined of compiled.outlined) { + CompilerError.invariant(outlined.fn.outlined.length === 0, { + reason: 'Unexpected nested outlined functions', + loc: outlined.fn.loc, + }); + const fn = insertNewOutlinedFunctionNode( + program, + current.fn, + outlined.fn, + ); + fn.skip(); + programContext.alreadyCompiled.add(fn.node); + if (outlined.type !== null) { + queue.push({ + kind: 'outlined', + fn, + fnType: outlined.type, + }); + } + } + compiledFns.push({ + kind: current.kind, + originalFn: current.fn, + compiledFn: compiled, + }); + } + } + + // Avoid modifying the program if we find a program level opt-out + if (findDirectiveDisablingMemoization(program.node.directives) != null) { + return null; + } + + // Insert React Compiler generated functions into the Babel AST + applyCompiledFunctions(program, compiledFns, pass, programContext); + + return { + retryErrors: programContext.retryErrors, + inferredEffectLocations: programContext.inferredEffectLocations, + }; +} + +type CompileSource = { + kind: 'original' | 'outlined'; + fn: BabelFn; + fnType: ReactFunctionType; +}; +/** + * Find all React components and hooks that need to be compiled + * + * @returns An array of React functions from @param program to transform + */ +function findFunctionsToCompile( + program: NodePath, + pass: CompilerPass, + programContext: ProgramContext, +): Array { + const queue: Array = []; const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => { - const fnType = getReactFunctionType(fn, pass, environment); - if (fnType === null || ALREADY_COMPILED.has(fn.node)) { + const fnType = getReactFunctionType(fn, pass); + if (fnType === null || programContext.alreadyCompiled.has(fn.node)) { return; } @@ -331,7 +408,7 @@ export function compileProgram( * traversal will loop infinitely. * Ensure we avoid visiting the original function again. */ - ALREADY_COMPILED.add(fn.node); + programContext.alreadyCompiled.add(fn.node); fn.skip(); queue.push({kind: 'original', fn, fnType}); @@ -346,7 +423,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, ClassExpression(node: NodePath) { @@ -355,7 +431,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, FunctionDeclaration: traverseFunction, @@ -370,205 +445,196 @@ export function compileProgram( filename: pass.filename ?? null, }, ); - const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; - const inferredEffectLocations = new Set(); - const processFn = ( - fn: BabelFn, - fnType: ReactFunctionType, - ): null | CodegenFunction => { - let optInDirectives: Array = []; - let optOutDirectives: Array = []; - if (fn.node.body.type === 'BlockStatement') { - optInDirectives = findDirectiveEnablingMemoization( - fn.node.body.directives, - ); - optOutDirectives = findDirectiveDisablingMemoization( - fn.node.body.directives, - ); - } + return queue; +} - /** - * Note that Babel does not attach comment nodes to nodes; they are dangling off of the - * Program node itself. We need to figure out whether an eslint suppression range - * applies to this function first. - */ - const suppressionsInFunction = filterSuppressionsThatAffectFunction( - suppressions, - fn, - ); - let compileResult: - | {kind: 'compile'; compiledFn: CodegenFunction} - | {kind: 'error'; error: unknown}; - if (suppressionsInFunction.length > 0) { - compileResult = { - kind: 'error', - error: suppressionsToCompilerError(suppressionsInFunction), - }; +/** + * Try to compile a source function, taking into account all local suppressions, + * opt-ins, and opt-outs. + * + * Errors encountered during compilation are either logged (if recoverable) or + * thrown (if non-recoverable). + * + * @returns the compiled function or null if the function was skipped (due to + * config settings and/or outputs) + */ +function processFn( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): null | CodegenFunction { + let directives; + if (fn.node.body.type !== 'BlockStatement') { + directives = {optIn: null, optOut: null}; + } else { + directives = { + optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + }; + } + + const compileResult = tryCompileFunction(fn, fnType, programContext); + + if (compileResult.kind === 'error') { + if (directives.optOut != null) { + logError(compileResult.error, programContext, fn.node.loc ?? null); } else { - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'all_features', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - } catch (err) { - compileResult = {kind: 'error', error: err}; - } + handleError(compileResult.error, programContext, fn.node.loc ?? null); } - - if (compileResult.kind === 'error') { - /** - * If an opt out directive is present, log only instead of throwing and don't mark as - * containing a critical error. - */ - if (optOutDirectives.length > 0) { - logError(compileResult.error, pass, fn.node.loc ?? null); - } else { - handleError(compileResult.error, pass, fn.node.loc ?? null); - } - // If non-memoization features are enabled, retry regardless of error kind - if ( - !(environment.enableFire || environment.inferEffectDependencies != null) - ) { - return null; - } - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'no_inferred_memo', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - if ( - !compileResult.compiledFn.hasFireRewrite && - !compileResult.compiledFn.hasInferredEffect - ) { - return null; - } - } catch (err) { - // TODO: we might want to log error here, but this will also result in duplicate logging - if (err instanceof CompilerError) { - retryErrors.push({fn, error: err}); - } - return null; - } - } - - /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. - */ - if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) { - for (const directive of optOutDirectives) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSkip', - fnLoc: fn.node.body.loc ?? null, - reason: `Skipped due to '${directive.value.value}' directive.`, - loc: directive.loc ?? null, - }); - } - return null; - } - - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSuccess', - fnLoc: fn.node.loc ?? null, - fnName: compileResult.compiledFn.id?.name ?? null, - memoSlots: compileResult.compiledFn.memoSlotsUsed, - memoBlocks: compileResult.compiledFn.memoBlocks, - memoValues: compileResult.compiledFn.memoValues, - prunedMemoBlocks: compileResult.compiledFn.prunedMemoBlocks, - prunedMemoValues: compileResult.compiledFn.prunedMemoValues, - }); - - /** - * Always compile functions with opt in directives. - */ - if (optInDirectives.length > 0) { - return compileResult.compiledFn; - } else if (pass.opts.compilationMode === 'annotation') { - /** - * No opt-in directive in annotation mode, so don't insert the compiled function. - */ - return null; - } - - if (!pass.opts.noEmit) { - return compileResult.compiledFn; - } - /** - * inferEffectDependencies + noEmit is currently only used for linting. In - * this mode, add source locations for where the compiler *can* infer effect - * dependencies. - */ - for (const loc of compileResult.compiledFn.inferredEffectLocations) { - if (loc !== GeneratedSource) inferredEffectLocations.add(loc); - } - return null; - }; - - while (queue.length !== 0) { - const current = queue.shift()!; - const compiled = processFn(current.fn, current.fnType); - if (compiled === null) { - continue; - } - for (const outlined of compiled.outlined) { - CompilerError.invariant(outlined.fn.outlined.length === 0, { - reason: 'Unexpected nested outlined functions', - loc: outlined.fn.loc, - }); - const fn = insertNewOutlinedFunctionNode( - program, - current.fn, - outlined.fn, - ); - fn.skip(); - ALREADY_COMPILED.add(fn.node); - if (outlined.type !== null) { - queue.push({ - kind: 'outlined', - fn, - fnType: outlined.type, - }); - } - } - compiledFns.push({ - kind: current.kind, - compiledFn: compiled, - originalFn: current.fn, - }); + return retryCompileFunction(fn, fnType, programContext); } /** - * Do not modify source if there is a module scope level opt out directive. + * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler + * for validation but we don't mutate the babel AST. This allows us to flag if there is an + * unused 'use no forget/memo' directive. */ - const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization( - program.node.directives, - ); - if (moduleScopeOptOutDirectives.length > 0) { + if ( + programContext.opts.ignoreUseNoForget === false && + directives.optOut != null + ) { + programContext.logEvent({ + kind: 'CompileSkip', + fnLoc: fn.node.body.loc ?? null, + reason: `Skipped due to '${directives.optOut.value}' directive.`, + loc: directives.optOut.loc ?? null, + }); return null; } - /* - * Only insert Forget-ified functions if we have not encountered a critical - * error elsewhere in the file, regardless of bailout mode. + const compiledFn = compileResult.compiledFn; + programContext.logEvent({ + kind: 'CompileSuccess', + fnLoc: fn.node.loc ?? null, + fnName: compiledFn.id?.name ?? null, + memoSlots: compiledFn.memoSlotsUsed, + memoBlocks: compiledFn.memoBlocks, + memoValues: compiledFn.memoValues, + prunedMemoBlocks: compiledFn.prunedMemoBlocks, + prunedMemoValues: compiledFn.prunedMemoValues, + }); + + /** + * Always compile functions with opt in directives. */ + if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else if (!programContext.opts.noEmit) { + return compiledFn; + } + + /** + * inferEffectDependencies + noEmit is currently only used for linting. In + * this mode, add source locations for where the compiler *can* infer effect + * dependencies. + */ + for (const loc of compileResult.compiledFn.inferredEffectLocations) { + if (loc !== GeneratedSource) { + programContext.inferredEffectLocations.add(loc); + } + } + return null; +} + +function tryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): + | {kind: 'compile'; compiledFn: CodegenFunction} + | {kind: 'error'; error: unknown} { + /** + * Note that Babel does not attach comment nodes to nodes; they are dangling off of the + * Program node itself. We need to figure out whether an eslint suppression range + * applies to this function first. + */ + const suppressionsInFunction = filterSuppressionsThatAffectFunction( + programContext.suppressions, + fn, + ); + if (suppressionsInFunction.length > 0) { + return { + kind: 'error', + error: suppressionsToCompilerError(suppressionsInFunction), + }; + } + + try { + return { + kind: 'compile', + compiledFn: compileFn( + fn, + programContext.opts.environment, + fnType, + 'all_features', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ), + }; + } catch (err) { + return {kind: 'error', error: err}; + } +} + +/** + * If non-memo feature flags are enabled, retry compilation with a more minimal + * feature set. + * + * @returns a CodegenFunction if retry was successful + */ +function retryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): CodegenFunction | null { + const environment = programContext.opts.environment; + if ( + !(environment.enableFire || environment.inferEffectDependencies != null) + ) { + return null; + } + try { + const retryResult = compileFn( + fn, + environment, + fnType, + 'no_inferred_memo', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ); + + if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) { + return null; + } + return retryResult; + } catch (err) { + // TODO: we might want to log error here, but this will also result in duplicate logging + if (err instanceof CompilerError) { + programContext.retryErrors.push({fn, error: err}); + } + return null; + } +} + +/** + * Applies React Compiler generated functions to the babel AST by replacing + * existing functions in place or inserting new declarations. + */ +function applyCompiledFunctions( + program: NodePath, + compiledFns: Array, + pass: CompilerPass, + programContext: ProgramContext, +): void { const referencedBeforeDeclared = pass.opts.gating != null ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) @@ -576,6 +642,7 @@ export function compileProgram( for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); + programContext.alreadyCompiled.add(transformedFn); if (referencedBeforeDeclared != null && kind === 'original') { CompilerError.invariant(pass.opts.gating != null, { @@ -598,7 +665,6 @@ export function compileProgram( if (compiledFns.length > 0) { addImportsToProgram(program, programContext); } - return {retryErrors, inferredEffectLocations}; } function shouldSkipCompilation( @@ -640,14 +706,10 @@ function shouldSkipCompilation( function getReactFunctionType( fn: BabelFn, pass: CompilerPass, - /** - * TODO(mofeiZ): remove once we validate PluginOptions with Zod - */ - environment: EnvironmentConfig, ): ReactFunctionType | null { - const hookPattern = environment.hookPattern; + const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0) + if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 5f6c6986e0..e288c227ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -18,7 +18,7 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {Environment} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; -import {CompileProgramResult} from './Program'; +import {CompileProgramMetadata} from './Program'; function throwInvalidReact( options: Omit, @@ -109,7 +109,7 @@ export default function validateNoUntransformedReferences( filename: string | null, logger: Logger | null, env: EnvironmentConfig, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const moduleLoadChecks = new Map< string, @@ -236,7 +236,7 @@ function transformProgram( moduleLoadChecks: Map>, filename: string | null, logger: Logger | null, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const traversalState: TraversalState = { shouldInvalidateScopes: true, From d5358f8af6b1d8a313a4dcaf411afbee198da330 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 11:27:18 -0400 Subject: [PATCH 374/422] [compiler][entrypoint] Fix edgecases for noEmit and opt-outs Title --- .../src/Entrypoint/Imports.ts | 9 +++- .../src/Entrypoint/Program.ts | 47 ++++++++++++------- ...bailout-nopanic-shouldnt-outline.expect.md | 3 -- .../compiler/use-memo-noemit.expect.md | 15 +----- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index d5cac921e9..d8f3ad0904 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -19,7 +19,11 @@ import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; import {LoggerEvent, PluginOptions} from './Options'; -import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import { + BabelFn, + findDirectiveDisablingMemoization, + getReactCompilerRuntimeModule, +} from './Program'; import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( @@ -70,6 +74,7 @@ export class ProgramContext { code: string | null; reactRuntimeModule: string; suppressions: Array; + hasModuleScopeOptOut: boolean; /* * This is a hack to work around what seems to be a Babel bug. Babel doesn't @@ -101,6 +106,8 @@ export class ProgramContext { this.code = code; this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); this.suppressions = suppressions; + this.hasModuleScopeOptOut = + findDirectiveDisablingMemoization(program.node.directives) != null; } isHookName(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 58e3c7de01..6ed9295bc8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -368,7 +368,19 @@ export function compileProgram( } // Avoid modifying the program if we find a program level opt-out - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } return null; } @@ -513,21 +525,6 @@ function processFn( prunedMemoValues: compiledFn.prunedMemoValues, }); - /** - * Always compile functions with opt in directives. - */ - if (directives.optIn != null) { - return compiledFn; - } else if (programContext.opts.compilationMode === 'annotation') { - /** - * If no opt-in directive is found and the compiler is configured in - * annotation mode, don't insert the compiled function. - */ - return null; - } else if (!programContext.opts.noEmit) { - return compiledFn; - } - /** * inferEffectDependencies + noEmit is currently only used for linting. In * this mode, add source locations for where the compiler *can* infer effect @@ -538,7 +535,23 @@ function processFn( programContext.inferredEffectLocations.add(loc); } } - return null; + + /** + * Always compile functions with opt in directives. + */ + if (programContext.hasModuleScopeOptOut || programContext.opts.noEmit) { + return null; + } else if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else { + return compiledFn; + } } function tryCompileFunction( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md index f24d949205..cfbaa34568 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -20,9 +20,6 @@ function Foo() { function Foo() { return ; } -function _temp() { - return alert("hello!"); -} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md index dfc831555f..c47501945b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -19,22 +19,11 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @noEmit +// @noEmit function Foo() { "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return alert("hello!"); + return ; } export const FIXTURE_ENTRYPOINT = { From 672e66bba8001e98f4942382bb3987a4af3cb8d3 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 11:27:18 -0400 Subject: [PATCH 375/422] [compiler][gating] Experimental directive based gating (todo: fill in summary) --- .../src/Entrypoint/Options.ts | 45 ++++++ .../src/Entrypoint/Program.ts | 132 +++++++++++++++--- .../dynamic-gating-bailout-nopanic.expect.md | 66 +++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 +++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 +++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 +++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 ++ 16 files changed, 514 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..ebf9f2467d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,27 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +269,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +318,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 6ed9295bc8..9aca4a1469 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,54 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else { + return Ok( + result.length > 0 + ? { + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + } + : null, + ); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -475,12 +533,31 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we should + * fall back to the safest option which is to not optimize the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -648,25 +725,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -722,8 +805,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.isOk() && optInDirectives.unwrap() !== null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From 2036a971e53f84b551b5f37eca9008c053b17bb1 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 11:27:18 -0400 Subject: [PATCH 376/422] [compiler][be] Make program traversal more readable React Compiler's program traversal logic is pretty lengthy and complex as we've added a lot of features piecemeal. `compileProgram` is 300+ lines long and has confusing control flow (defining helpers inline, invoking visitors, mutating-asts-while-iterating, mutating global `ALREADY_COMPILED` state). - Moved more stuff to `ProgramContext` - Separated `compileProgram` into a bunch of helpers Will come back and add more comments inline --- .../src/Entrypoint/Imports.ts | 62 +- .../src/Entrypoint/Program.ts | 567 ++++++++++-------- .../ValidateNoUntransformedReferences.ts | 6 +- .../bailout-retry/retry-no-emit.expect.md | 64 ++ .../bailout-retry/retry-no-emit.js | 19 + 5 files changed, 455 insertions(+), 263 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 704f696b3b..d5cac921e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -18,8 +18,9 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; -import {CompilerReactTarget} from './Options'; -import {getReactCompilerRuntimeModule} from './Program'; +import {LoggerEvent, PluginOptions} from './Options'; +import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( path: NodePath, @@ -52,32 +53,61 @@ export function validateRestrictedImports( } } +type ProgramContextOptions = { + program: NodePath; + suppressions: Array; + opts: PluginOptions; + filename: string | null; + code: string | null; +}; export class ProgramContext { - /* Program and environment context */ + /** + * Program and environment context + */ scope: BabelScope; + opts: PluginOptions; + filename: string | null; + code: string | null; reactRuntimeModule: string; - hookPattern: string | null; + suppressions: Array; + /* + * This is a hack to work around what seems to be a Babel bug. Babel doesn't + * consistently respect the `skip()` function to avoid revisiting a node within + * a pass, so we use this set to track nodes that we have compiled. + */ + alreadyCompiled: WeakSet | Set = new (WeakSet ?? Set)(); // known generated or referenced identifiers in the program knownReferencedNames: Set = new Set(); // generated imports imports: Map> = new Map(); - constructor( - program: NodePath, - reactRuntimeModule: CompilerReactTarget, - hookPattern: string | null, - ) { - this.hookPattern = hookPattern; + /** + * Metadata from compilation + */ + retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; + inferredEffectLocations: Set = new Set(); + + constructor({ + program, + suppressions, + opts, + filename, + code, + }: ProgramContextOptions) { this.scope = program.scope; - this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule); + this.opts = opts; + this.filename = filename; + this.code = code; + this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); + this.suppressions = suppressions; } isHookName(name: string): boolean { - if (this.hookPattern == null) { + if (this.opts.environment.hookPattern == null) { return isHookName(name); } else { - const match = new RegExp(this.hookPattern).exec(name); + const match = new RegExp(this.opts.environment.hookPattern).exec(name); return ( match != null && typeof match[1] === 'string' && isHookName(match[1]) ); @@ -179,6 +209,12 @@ export class ProgramContext { }); return Err(error); } + + logEvent(event: LoggerEvent): void { + if (this.opts.logger != null) { + this.opts.logger.logEvent(this.filename, event); + } + } } function getExistingImports( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 34347f10b8..6cd674e46e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment'; +import {ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -43,17 +43,21 @@ export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); export function findDirectiveEnablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), + ) ?? null ); } export function findDirectiveDisablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_OUT_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_OUT_DIRECTIVES.has(directive.value.value), + ) ?? null ); } @@ -88,13 +92,16 @@ export type CompileResult = { function logError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - if (pass.opts.logger) { + if (context.opts.logger) { if (err instanceof CompilerError) { for (const detail of err.details) { - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, detail: detail.options, @@ -108,7 +115,7 @@ function logError( stringifiedError = err?.toString() ?? '[ null ]'; } - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'PipelineError', fnLoc, data: stringifiedError, @@ -118,13 +125,17 @@ function logError( } function handleError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - logError(err, pass, fnLoc); + logError(err, context, fnLoc); if ( - pass.opts.panicThreshold === 'all_errors' || - (pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) || + context.opts.panicThreshold === 'all_errors' || + (context.opts.panicThreshold === 'critical_errors' && + isCriticalError(err)) || isConfigError(err) // Always throws regardless of panic threshold ) { throw err; @@ -187,7 +198,6 @@ export function createNewFunctionNode( } } // Avoid visiting the new transformed version - ALREADY_COMPILED.add(transformedFn); return transformedFn; } @@ -239,13 +249,6 @@ function insertNewOutlinedFunctionNode( } } -/* - * This is a hack to work around what seems to be a Babel bug. Babel doesn't - * consistently respect the `skip()` function to avoid revisiting a node within - * a pass, so we use this set to track nodes that we have compiled. - */ -const ALREADY_COMPILED: WeakSet | Set = new (WeakSet ?? Set)(); - const DEFAULT_ESLINT_SUPPRESSIONS = [ 'react-hooks/exhaustive-deps', 'react-hooks/rules-of-hooks', @@ -268,41 +271,43 @@ function isFilePartOfSources( return false; } -export type CompileProgramResult = { +export type CompileProgramMetadata = { retryErrors: Array<{fn: BabelFn; error: CompilerError}>; inferredEffectLocations: Set; }; /** - * `compileProgram` is directly invoked by the react-compiler babel plugin, so - * exceptions thrown by this function will fail the babel build. - * - call `handleError` if your error is recoverable. - * Unless the error is a warning / info diagnostic, compilation of a function - * / entire file should also be skipped. - * - throw an exception if the error is fatal / not recoverable. - * Examples of this are invalid compiler configs or failure to codegen outlined - * functions *after* already emitting optimized components / hooks that invoke - * the outlined functions. + * Main entrypoint for React Compiler. + * + * @param program The Babel program node to compile + * @param pass Compiler configuration and context + * @returns Compilation results or null if compilation was skipped */ export function compileProgram( program: NodePath, pass: CompilerPass, -): CompileProgramResult | null { +): CompileProgramMetadata | null { + /** + * This is directly invoked by the react-compiler babel plugin, so exceptions + * thrown by this function will fail the babel build. + * - call `handleError` if your error is recoverable. + * Unless the error is a warning / info diagnostic, compilation of a function + * / entire file should also be skipped. + * - throw an exception if the error is fatal / not recoverable. + * Examples of this are invalid compiler configs or failure to codegen outlined + * functions *after* already emitting optimized components / hooks that invoke + * the outlined functions. + */ if (shouldSkipCompilation(program, pass)) { return null; } - - const environment = pass.opts.environment; - const restrictedImportsErr = validateRestrictedImports(program, environment); + const restrictedImportsErr = validateRestrictedImports( + program, + pass.opts.environment, + ); if (restrictedImportsErr) { handleError(restrictedImportsErr, pass, null); return null; } - - const programContext = new ProgramContext( - program, - pass.opts.target, - environment.hookPattern, - ); /* * Record lint errors and critical errors as depending on Forget's config, * we may still need to run Forget's analysis on every function (even if we @@ -313,16 +318,88 @@ export function compileProgram( pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions, ); - const queue: Array<{ - kind: 'original' | 'outlined'; - fn: BabelFn; - fnType: ReactFunctionType; - }> = []; + + const programContext = new ProgramContext({ + program: program, + opts: pass.opts, + filename: pass.filename, + code: pass.code, + suppressions, + }); + + const queue: Array = findFunctionsToCompile( + program, + pass, + programContext, + ); const compiledFns: Array = []; + while (queue.length !== 0) { + const current = queue.shift()!; + const compiled = processFn(current.fn, current.fnType, programContext); + + if (compiled != null) { + for (const outlined of compiled.outlined) { + CompilerError.invariant(outlined.fn.outlined.length === 0, { + reason: 'Unexpected nested outlined functions', + loc: outlined.fn.loc, + }); + const fn = insertNewOutlinedFunctionNode( + program, + current.fn, + outlined.fn, + ); + fn.skip(); + programContext.alreadyCompiled.add(fn.node); + if (outlined.type !== null) { + queue.push({ + kind: 'outlined', + fn, + fnType: outlined.type, + }); + } + } + compiledFns.push({ + kind: current.kind, + originalFn: current.fn, + compiledFn: compiled, + }); + } + } + + // Avoid modifying the program if we find a program level opt-out + if (findDirectiveDisablingMemoization(program.node.directives) != null) { + return null; + } + + // Insert React Compiler generated functions into the Babel AST + applyCompiledFunctions(program, compiledFns, pass, programContext); + + return { + retryErrors: programContext.retryErrors, + inferredEffectLocations: programContext.inferredEffectLocations, + }; +} + +type CompileSource = { + kind: 'original' | 'outlined'; + fn: BabelFn; + fnType: ReactFunctionType; +}; +/** + * Find all React components and hooks that need to be compiled + * + * @returns An array of React functions from @param program to transform + */ +function findFunctionsToCompile( + program: NodePath, + pass: CompilerPass, + programContext: ProgramContext, +): Array { + const queue: Array = []; const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => { - const fnType = getReactFunctionType(fn, pass, environment); - if (fnType === null || ALREADY_COMPILED.has(fn.node)) { + const fnType = getReactFunctionType(fn, pass); + if (fnType === null || programContext.alreadyCompiled.has(fn.node)) { return; } @@ -331,7 +408,7 @@ export function compileProgram( * traversal will loop infinitely. * Ensure we avoid visiting the original function again. */ - ALREADY_COMPILED.add(fn.node); + programContext.alreadyCompiled.add(fn.node); fn.skip(); queue.push({kind: 'original', fn, fnType}); @@ -346,7 +423,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, ClassExpression(node: NodePath) { @@ -355,7 +431,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, FunctionDeclaration: traverseFunction, @@ -370,205 +445,207 @@ export function compileProgram( filename: pass.filename ?? null, }, ); - const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; - const inferredEffectLocations = new Set(); - const processFn = ( - fn: BabelFn, - fnType: ReactFunctionType, - ): null | CodegenFunction => { - let optInDirectives: Array = []; - let optOutDirectives: Array = []; - if (fn.node.body.type === 'BlockStatement') { - optInDirectives = findDirectiveEnablingMemoization( - fn.node.body.directives, - ); - optOutDirectives = findDirectiveDisablingMemoization( - fn.node.body.directives, - ); - } + return queue; +} - /** - * Note that Babel does not attach comment nodes to nodes; they are dangling off of the - * Program node itself. We need to figure out whether an eslint suppression range - * applies to this function first. - */ - const suppressionsInFunction = filterSuppressionsThatAffectFunction( - suppressions, - fn, - ); - let compileResult: - | {kind: 'compile'; compiledFn: CodegenFunction} - | {kind: 'error'; error: unknown}; - if (suppressionsInFunction.length > 0) { - compileResult = { - kind: 'error', - error: suppressionsToCompilerError(suppressionsInFunction), - }; +/** + * Try to compile a source function, taking into account all local suppressions, + * opt-ins, and opt-outs. + * + * Errors encountered during compilation are either logged (if recoverable) or + * thrown (if non-recoverable). + * + * @returns the compiled function or null if the function was skipped (due to + * config settings and/or outputs) + */ +function processFn( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): null | CodegenFunction { + let directives; + if (fn.node.body.type !== 'BlockStatement') { + directives = {optIn: null, optOut: null}; + } else { + directives = { + optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + }; + } + + let compileResult = tryCompileFunction(fn, fnType, programContext); + + if (compileResult.kind === 'error') { + if (directives.optOut != null) { + logError(compileResult.error, programContext, fn.node.loc ?? null); } else { - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'all_features', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - } catch (err) { - compileResult = {kind: 'error', error: err}; - } + handleError(compileResult.error, programContext, fn.node.loc ?? null); } - - if (compileResult.kind === 'error') { - /** - * If an opt out directive is present, log only instead of throwing and don't mark as - * containing a critical error. - */ - if (optOutDirectives.length > 0) { - logError(compileResult.error, pass, fn.node.loc ?? null); - } else { - handleError(compileResult.error, pass, fn.node.loc ?? null); - } - // If non-memoization features are enabled, retry regardless of error kind - if ( - !(environment.enableFire || environment.inferEffectDependencies != null) - ) { - return null; - } - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'no_inferred_memo', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - if ( - !compileResult.compiledFn.hasFireRewrite && - !compileResult.compiledFn.hasInferredEffect - ) { - return null; - } - } catch (err) { - // TODO: we might want to log error here, but this will also result in duplicate logging - if (err instanceof CompilerError) { - retryErrors.push({fn, error: err}); - } - return null; - } - } - - /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. - */ - if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) { - for (const directive of optOutDirectives) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSkip', - fnLoc: fn.node.body.loc ?? null, - reason: `Skipped due to '${directive.value.value}' directive.`, - loc: directive.loc ?? null, - }); - } + const retryResult = retryCompileFunction(fn, fnType, programContext); + if (retryResult == null) { return null; } - - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSuccess', - fnLoc: fn.node.loc ?? null, - fnName: compileResult.compiledFn.id?.name ?? null, - memoSlots: compileResult.compiledFn.memoSlotsUsed, - memoBlocks: compileResult.compiledFn.memoBlocks, - memoValues: compileResult.compiledFn.memoValues, - prunedMemoBlocks: compileResult.compiledFn.prunedMemoBlocks, - prunedMemoValues: compileResult.compiledFn.prunedMemoValues, - }); - - /** - * Always compile functions with opt in directives. - */ - if (optInDirectives.length > 0) { - return compileResult.compiledFn; - } else if (pass.opts.compilationMode === 'annotation') { - /** - * No opt-in directive in annotation mode, so don't insert the compiled function. - */ - return null; - } - - if (!pass.opts.noEmit) { - return compileResult.compiledFn; - } - /** - * inferEffectDependencies + noEmit is currently only used for linting. In - * this mode, add source locations for where the compiler *can* infer effect - * dependencies. - */ - for (const loc of compileResult.compiledFn.inferredEffectLocations) { - if (loc !== GeneratedSource) inferredEffectLocations.add(loc); - } - return null; - }; - - while (queue.length !== 0) { - const current = queue.shift()!; - const compiled = processFn(current.fn, current.fnType); - if (compiled === null) { - continue; - } - for (const outlined of compiled.outlined) { - CompilerError.invariant(outlined.fn.outlined.length === 0, { - reason: 'Unexpected nested outlined functions', - loc: outlined.fn.loc, - }); - const fn = insertNewOutlinedFunctionNode( - program, - current.fn, - outlined.fn, - ); - fn.skip(); - ALREADY_COMPILED.add(fn.node); - if (outlined.type !== null) { - queue.push({ - kind: 'outlined', - fn, - fnType: outlined.type, - }); - } - } - compiledFns.push({ - kind: current.kind, - compiledFn: compiled, - originalFn: current.fn, - }); + compileResult = { + kind: 'compile', + compiledFn: retryResult, + }; } /** - * Do not modify source if there is a module scope level opt out directive. + * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler + * for validation but we don't mutate the babel AST. This allows us to flag if there is an + * unused 'use no forget/memo' directive. */ - const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization( - program.node.directives, - ); - if (moduleScopeOptOutDirectives.length > 0) { + if ( + programContext.opts.ignoreUseNoForget === false && + directives.optOut != null + ) { + programContext.logEvent({ + kind: 'CompileSkip', + fnLoc: fn.node.body.loc ?? null, + reason: `Skipped due to '${directives.optOut.value}' directive.`, + loc: directives.optOut.loc ?? null, + }); return null; } - /* - * Only insert Forget-ified functions if we have not encountered a critical - * error elsewhere in the file, regardless of bailout mode. + const compiledFn = compileResult.compiledFn; + programContext.logEvent({ + kind: 'CompileSuccess', + fnLoc: fn.node.loc ?? null, + fnName: compiledFn.id?.name ?? null, + memoSlots: compiledFn.memoSlotsUsed, + memoBlocks: compiledFn.memoBlocks, + memoValues: compiledFn.memoValues, + prunedMemoBlocks: compiledFn.prunedMemoBlocks, + prunedMemoValues: compiledFn.prunedMemoValues, + }); + + /** + * Always compile functions with opt in directives. */ + if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else if (!programContext.opts.noEmit) { + return compiledFn; + } + + /** + * inferEffectDependencies + noEmit is currently only used for linting. In + * this mode, add source locations for where the compiler *can* infer effect + * dependencies. + */ + for (const loc of compileResult.compiledFn.inferredEffectLocations) { + if (loc !== GeneratedSource) { + programContext.inferredEffectLocations.add(loc); + } + } + return null; +} + +function tryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): + | {kind: 'compile'; compiledFn: CodegenFunction} + | {kind: 'error'; error: unknown} { + /** + * Note that Babel does not attach comment nodes to nodes; they are dangling off of the + * Program node itself. We need to figure out whether an eslint suppression range + * applies to this function first. + */ + const suppressionsInFunction = filterSuppressionsThatAffectFunction( + programContext.suppressions, + fn, + ); + if (suppressionsInFunction.length > 0) { + return { + kind: 'error', + error: suppressionsToCompilerError(suppressionsInFunction), + }; + } + + try { + return { + kind: 'compile', + compiledFn: compileFn( + fn, + programContext.opts.environment, + fnType, + 'all_features', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ), + }; + } catch (err) { + return {kind: 'error', error: err}; + } +} + +/** + * If non-memo feature flags are enabled, retry compilation with a more minimal + * feature set. + * + * @returns a CodegenFunction if retry was successful + */ +function retryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): CodegenFunction | null { + const environment = programContext.opts.environment; + if ( + !(environment.enableFire || environment.inferEffectDependencies != null) + ) { + return null; + } + /** + * Note that function suppressions are not checked in the retry pipeline, as + * they only affect auto-memoization features. + */ + try { + const retryResult = compileFn( + fn, + environment, + fnType, + 'no_inferred_memo', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ); + + if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) { + return null; + } + return retryResult; + } catch (err) { + // TODO: we might want to log error here, but this will also result in duplicate logging + if (err instanceof CompilerError) { + programContext.retryErrors.push({fn, error: err}); + } + return null; + } +} + +/** + * Applies React Compiler generated functions to the babel AST by replacing + * existing functions in place or inserting new declarations. + */ +function applyCompiledFunctions( + program: NodePath, + compiledFns: Array, + pass: CompilerPass, + programContext: ProgramContext, +): void { const referencedBeforeDeclared = pass.opts.gating != null ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) @@ -576,6 +653,7 @@ export function compileProgram( for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); + programContext.alreadyCompiled.add(transformedFn); if (referencedBeforeDeclared != null && kind === 'original') { CompilerError.invariant(pass.opts.gating != null, { @@ -598,7 +676,6 @@ export function compileProgram( if (compiledFns.length > 0) { addImportsToProgram(program, programContext); } - return {retryErrors, inferredEffectLocations}; } function shouldSkipCompilation( @@ -640,14 +717,10 @@ function shouldSkipCompilation( function getReactFunctionType( fn: BabelFn, pass: CompilerPass, - /** - * TODO(mofeiZ): remove once we validate PluginOptions with Zod - */ - environment: EnvironmentConfig, ): ReactFunctionType | null { - const hookPattern = environment.hookPattern; + const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0) + if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 5f6c6986e0..e288c227ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -18,7 +18,7 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {Environment} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; -import {CompileProgramResult} from './Program'; +import {CompileProgramMetadata} from './Program'; function throwInvalidReact( options: Omit, @@ -109,7 +109,7 @@ export default function validateNoUntransformedReferences( filename: string | null, logger: Logger | null, env: EnvironmentConfig, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const moduleLoadChecks = new Map< string, @@ -236,7 +236,7 @@ function transformProgram( moduleLoadChecks: Map>, filename: string | null, logger: Logger | null, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const traversalState: TraversalState = { shouldInvalidateScopes: true, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.expect.md new file mode 100644 index 0000000000..bd70c0138d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo({ propVal }) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.js new file mode 100644 index 0000000000..d1dda06a04 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.js @@ -0,0 +1,19 @@ +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; From b13f5b34faff7c2539ecd70abd43045269eb3288 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 12:32:06 -0400 Subject: [PATCH 377/422] [compiler][entrypoint] Fix edgecases for noEmit and opt-outs Title --- .../src/Entrypoint/Imports.ts | 4 ++ .../src/Entrypoint/Program.ts | 49 ++++++++++++------- ...bailout-nopanic-shouldnt-outline.expect.md | 3 -- .../compiler/use-memo-noemit.expect.md | 15 +----- 4 files changed, 38 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index d5cac921e9..24ce37cf72 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -59,6 +59,7 @@ type ProgramContextOptions = { opts: PluginOptions; filename: string | null; code: string | null; + hasModuleScopeOptOut: boolean; }; export class ProgramContext { /** @@ -70,6 +71,7 @@ export class ProgramContext { code: string | null; reactRuntimeModule: string; suppressions: Array; + hasModuleScopeOptOut: boolean; /* * This is a hack to work around what seems to be a Babel bug. Babel doesn't @@ -94,6 +96,7 @@ export class ProgramContext { opts, filename, code, + hasModuleScopeOptOut, }: ProgramContextOptions) { this.scope = program.scope; this.opts = opts; @@ -101,6 +104,7 @@ export class ProgramContext { this.code = code; this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); this.suppressions = suppressions; + this.hasModuleScopeOptOut = hasModuleScopeOptOut; } isHookName(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 6cd674e46e..91c6c5ceea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -325,6 +325,8 @@ export function compileProgram( filename: pass.filename, code: pass.code, suppressions, + hasModuleScopeOptOut: + findDirectiveDisablingMemoization(program.node.directives) != null, }); const queue: Array = findFunctionsToCompile( @@ -368,7 +370,19 @@ export function compileProgram( } // Avoid modifying the program if we find a program level opt-out - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } return null; } @@ -520,21 +534,6 @@ function processFn( prunedMemoValues: compiledFn.prunedMemoValues, }); - /** - * Always compile functions with opt in directives. - */ - if (directives.optIn != null) { - return compiledFn; - } else if (programContext.opts.compilationMode === 'annotation') { - /** - * If no opt-in directive is found and the compiler is configured in - * annotation mode, don't insert the compiled function. - */ - return null; - } else if (!programContext.opts.noEmit) { - return compiledFn; - } - /** * inferEffectDependencies + noEmit is currently only used for linting. In * this mode, add source locations for where the compiler *can* infer effect @@ -545,7 +544,23 @@ function processFn( programContext.inferredEffectLocations.add(loc); } } - return null; + + /** + * Always compile functions with opt in directives. + */ + if (programContext.hasModuleScopeOptOut || programContext.opts.noEmit) { + return null; + } else if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else { + return compiledFn; + } } function tryCompileFunction( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md index f24d949205..cfbaa34568 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -20,9 +20,6 @@ function Foo() { function Foo() { return ; } -function _temp() { - return alert("hello!"); -} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md index dfc831555f..c47501945b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -19,22 +19,11 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @noEmit +// @noEmit function Foo() { "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return alert("hello!"); + return ; } export const FIXTURE_ENTRYPOINT = { From 4c48a872308f5b24ff838c82f2757d315418d81b Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 12:34:49 -0400 Subject: [PATCH 378/422] [compiler][gating] Experimental directive based gating (todo: fill in summary) --- .../src/Entrypoint/Options.ts | 46 ++++++ .../src/Entrypoint/Program.ts | 143 +++++++++++++++--- .../dynamic-gating-bailout-nopanic.expect.md | 66 ++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 ++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 ++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ .../dynamic-gating-invalid-multiple.expect.md | 45 ++++++ .../gating/dynamic-gating-invalid-multiple.js | 12 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 ++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 + 18 files changed, 583 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..96cce887d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,28 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + * ``` + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +270,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +319,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 91c6c5ceea..8449b40ca1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,64 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else if (result.length > 1) { + const error = new CompilerError(); + error.push({ + reason: `Multiple dynamic gating directives found`, + description: `Expected a single directive but found [${result + .map(r => r.directive.value.value) + .join(', ')}]`, + severity: ErrorSeverity.InvalidReact, + loc: result[0].directive.loc ?? null, + suggestions: null, + }); + return Err(error); + } else if (result.length === 1) { + return Ok({ + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + }); + } else { + return Ok(null); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -477,12 +545,32 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we handle + * the error and fall back to the safest option which is to not optimize + * the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -661,25 +749,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -735,8 +829,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.unwrapOr(null) != null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md new file mode 100644 index 0000000000..327adbe792 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + "use memo if(getTrue)"; + "use memo if(getFalse)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}} +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js new file mode 100644 index 0000000000..867ac8ee34 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js @@ -0,0 +1,12 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From 0f12ec0881129826a319f61bf43db9aa55fb402d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 12:34:49 -0400 Subject: [PATCH 379/422] [compiler][gating] Experimental directive based gating Adds `dynamicGating` as an experimental option for testing rollout DX at Meta. If specified, this enables dynamic gating which matches `use memo if(...)` directives. #### Example usage Input file ```js // @dynamicGating:{"source":"myModule"} export function MyComponent() { 'use memo if(isEnabled)'; return
...
; } ``` Compiler output ```js import {isEnabled} from 'myModule'; export const MyComponent = isEnabled() ? : ; ``` --- .../src/Entrypoint/Options.ts | 46 ++++++ .../src/Entrypoint/Program.ts | 143 +++++++++++++++--- .../dynamic-gating-annotation.expect.md | 50 ++++++ .../gating/dynamic-gating-annotation.js | 11 ++ .../dynamic-gating-bailout-nopanic.expect.md | 66 ++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 ++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 ++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ .../dynamic-gating-invalid-multiple.expect.md | 45 ++++++ .../gating/dynamic-gating-invalid-multiple.js | 12 ++ .../gating/dynamic-gating-noemit.expect.md | 37 +++++ .../compiler/gating/dynamic-gating-noemit.js | 11 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 ++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 + 22 files changed, 692 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..96cce887d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,28 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + * ``` + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +270,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +319,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 91c6c5ceea..8449b40ca1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,64 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else if (result.length > 1) { + const error = new CompilerError(); + error.push({ + reason: `Multiple dynamic gating directives found`, + description: `Expected a single directive but found [${result + .map(r => r.directive.value.value) + .join(', ')}]`, + severity: ErrorSeverity.InvalidReact, + loc: result[0].directive.loc ?? null, + suggestions: null, + }); + return Err(error); + } else if (result.length === 1) { + return Ok({ + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + }); + } else { + return Ok(null); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -477,12 +545,32 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we handle + * the error and fall back to the safest option which is to not optimize + * the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -661,25 +749,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -735,8 +829,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.unwrapOr(null) != null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md new file mode 100644 index 0000000000..364239e4e3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js new file mode 100644 index 0000000000..c30b30fe6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md new file mode 100644 index 0000000000..327adbe792 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + "use memo if(getTrue)"; + "use memo if(getFalse)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}} +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js new file mode 100644 index 0000000000..867ac8ee34 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js @@ -0,0 +1,12 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md new file mode 100644 index 0000000000..81ebd6dd9f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + "use memo if(getTrue)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js new file mode 100644 index 0000000000..97cf777a55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From 23c45fff5f3620ec60fc017846a859f0a2813a98 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 14:52:42 -0400 Subject: [PATCH 380/422] [compiler][be] Make program traversal more readable React Compiler's program traversal logic is pretty lengthy and complex as we've added a lot of features piecemeal. `compileProgram` is 300+ lines long and has confusing control flow (defining helpers inline, invoking visitors, mutating-asts-while-iterating, mutating global `ALREADY_COMPILED` state). - Moved more stuff to `ProgramContext` - Separated `compileProgram` into a bunch of helpers Will come back and add more comments inline --- .../src/Entrypoint/Imports.ts | 62 +- .../src/Entrypoint/Program.ts | 567 ++++++++++-------- .../ValidateNoUntransformedReferences.ts | 6 +- .../bailout-retry/retry-no-emit.expect.md | 64 ++ .../bailout-retry/retry-no-emit.js | 19 + .../no-emit-lint-repro.expect.md | 0 .../{ => no-emit}/no-emit-lint-repro.js | 0 .../no-emit/retry-no-emit.expect.md | 64 ++ .../no-emit/retry-no-emit.js | 19 + 9 files changed, 538 insertions(+), 263 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.js rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/{ => no-emit}/no-emit-lint-repro.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/{ => no-emit}/no-emit-lint-repro.js (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 704f696b3b..d5cac921e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -18,8 +18,9 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; -import {CompilerReactTarget} from './Options'; -import {getReactCompilerRuntimeModule} from './Program'; +import {LoggerEvent, PluginOptions} from './Options'; +import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( path: NodePath, @@ -52,32 +53,61 @@ export function validateRestrictedImports( } } +type ProgramContextOptions = { + program: NodePath; + suppressions: Array; + opts: PluginOptions; + filename: string | null; + code: string | null; +}; export class ProgramContext { - /* Program and environment context */ + /** + * Program and environment context + */ scope: BabelScope; + opts: PluginOptions; + filename: string | null; + code: string | null; reactRuntimeModule: string; - hookPattern: string | null; + suppressions: Array; + /* + * This is a hack to work around what seems to be a Babel bug. Babel doesn't + * consistently respect the `skip()` function to avoid revisiting a node within + * a pass, so we use this set to track nodes that we have compiled. + */ + alreadyCompiled: WeakSet | Set = new (WeakSet ?? Set)(); // known generated or referenced identifiers in the program knownReferencedNames: Set = new Set(); // generated imports imports: Map> = new Map(); - constructor( - program: NodePath, - reactRuntimeModule: CompilerReactTarget, - hookPattern: string | null, - ) { - this.hookPattern = hookPattern; + /** + * Metadata from compilation + */ + retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; + inferredEffectLocations: Set = new Set(); + + constructor({ + program, + suppressions, + opts, + filename, + code, + }: ProgramContextOptions) { this.scope = program.scope; - this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule); + this.opts = opts; + this.filename = filename; + this.code = code; + this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); + this.suppressions = suppressions; } isHookName(name: string): boolean { - if (this.hookPattern == null) { + if (this.opts.environment.hookPattern == null) { return isHookName(name); } else { - const match = new RegExp(this.hookPattern).exec(name); + const match = new RegExp(this.opts.environment.hookPattern).exec(name); return ( match != null && typeof match[1] === 'string' && isHookName(match[1]) ); @@ -179,6 +209,12 @@ export class ProgramContext { }); return Err(error); } + + logEvent(event: LoggerEvent): void { + if (this.opts.logger != null) { + this.opts.logger.logEvent(this.filename, event); + } + } } function getExistingImports( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 34347f10b8..6cd674e46e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment'; +import {ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -43,17 +43,21 @@ export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); export function findDirectiveEnablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), + ) ?? null ); } export function findDirectiveDisablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_OUT_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_OUT_DIRECTIVES.has(directive.value.value), + ) ?? null ); } @@ -88,13 +92,16 @@ export type CompileResult = { function logError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - if (pass.opts.logger) { + if (context.opts.logger) { if (err instanceof CompilerError) { for (const detail of err.details) { - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, detail: detail.options, @@ -108,7 +115,7 @@ function logError( stringifiedError = err?.toString() ?? '[ null ]'; } - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'PipelineError', fnLoc, data: stringifiedError, @@ -118,13 +125,17 @@ function logError( } function handleError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - logError(err, pass, fnLoc); + logError(err, context, fnLoc); if ( - pass.opts.panicThreshold === 'all_errors' || - (pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) || + context.opts.panicThreshold === 'all_errors' || + (context.opts.panicThreshold === 'critical_errors' && + isCriticalError(err)) || isConfigError(err) // Always throws regardless of panic threshold ) { throw err; @@ -187,7 +198,6 @@ export function createNewFunctionNode( } } // Avoid visiting the new transformed version - ALREADY_COMPILED.add(transformedFn); return transformedFn; } @@ -239,13 +249,6 @@ function insertNewOutlinedFunctionNode( } } -/* - * This is a hack to work around what seems to be a Babel bug. Babel doesn't - * consistently respect the `skip()` function to avoid revisiting a node within - * a pass, so we use this set to track nodes that we have compiled. - */ -const ALREADY_COMPILED: WeakSet | Set = new (WeakSet ?? Set)(); - const DEFAULT_ESLINT_SUPPRESSIONS = [ 'react-hooks/exhaustive-deps', 'react-hooks/rules-of-hooks', @@ -268,41 +271,43 @@ function isFilePartOfSources( return false; } -export type CompileProgramResult = { +export type CompileProgramMetadata = { retryErrors: Array<{fn: BabelFn; error: CompilerError}>; inferredEffectLocations: Set; }; /** - * `compileProgram` is directly invoked by the react-compiler babel plugin, so - * exceptions thrown by this function will fail the babel build. - * - call `handleError` if your error is recoverable. - * Unless the error is a warning / info diagnostic, compilation of a function - * / entire file should also be skipped. - * - throw an exception if the error is fatal / not recoverable. - * Examples of this are invalid compiler configs or failure to codegen outlined - * functions *after* already emitting optimized components / hooks that invoke - * the outlined functions. + * Main entrypoint for React Compiler. + * + * @param program The Babel program node to compile + * @param pass Compiler configuration and context + * @returns Compilation results or null if compilation was skipped */ export function compileProgram( program: NodePath, pass: CompilerPass, -): CompileProgramResult | null { +): CompileProgramMetadata | null { + /** + * This is directly invoked by the react-compiler babel plugin, so exceptions + * thrown by this function will fail the babel build. + * - call `handleError` if your error is recoverable. + * Unless the error is a warning / info diagnostic, compilation of a function + * / entire file should also be skipped. + * - throw an exception if the error is fatal / not recoverable. + * Examples of this are invalid compiler configs or failure to codegen outlined + * functions *after* already emitting optimized components / hooks that invoke + * the outlined functions. + */ if (shouldSkipCompilation(program, pass)) { return null; } - - const environment = pass.opts.environment; - const restrictedImportsErr = validateRestrictedImports(program, environment); + const restrictedImportsErr = validateRestrictedImports( + program, + pass.opts.environment, + ); if (restrictedImportsErr) { handleError(restrictedImportsErr, pass, null); return null; } - - const programContext = new ProgramContext( - program, - pass.opts.target, - environment.hookPattern, - ); /* * Record lint errors and critical errors as depending on Forget's config, * we may still need to run Forget's analysis on every function (even if we @@ -313,16 +318,88 @@ export function compileProgram( pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions, ); - const queue: Array<{ - kind: 'original' | 'outlined'; - fn: BabelFn; - fnType: ReactFunctionType; - }> = []; + + const programContext = new ProgramContext({ + program: program, + opts: pass.opts, + filename: pass.filename, + code: pass.code, + suppressions, + }); + + const queue: Array = findFunctionsToCompile( + program, + pass, + programContext, + ); const compiledFns: Array = []; + while (queue.length !== 0) { + const current = queue.shift()!; + const compiled = processFn(current.fn, current.fnType, programContext); + + if (compiled != null) { + for (const outlined of compiled.outlined) { + CompilerError.invariant(outlined.fn.outlined.length === 0, { + reason: 'Unexpected nested outlined functions', + loc: outlined.fn.loc, + }); + const fn = insertNewOutlinedFunctionNode( + program, + current.fn, + outlined.fn, + ); + fn.skip(); + programContext.alreadyCompiled.add(fn.node); + if (outlined.type !== null) { + queue.push({ + kind: 'outlined', + fn, + fnType: outlined.type, + }); + } + } + compiledFns.push({ + kind: current.kind, + originalFn: current.fn, + compiledFn: compiled, + }); + } + } + + // Avoid modifying the program if we find a program level opt-out + if (findDirectiveDisablingMemoization(program.node.directives) != null) { + return null; + } + + // Insert React Compiler generated functions into the Babel AST + applyCompiledFunctions(program, compiledFns, pass, programContext); + + return { + retryErrors: programContext.retryErrors, + inferredEffectLocations: programContext.inferredEffectLocations, + }; +} + +type CompileSource = { + kind: 'original' | 'outlined'; + fn: BabelFn; + fnType: ReactFunctionType; +}; +/** + * Find all React components and hooks that need to be compiled + * + * @returns An array of React functions from @param program to transform + */ +function findFunctionsToCompile( + program: NodePath, + pass: CompilerPass, + programContext: ProgramContext, +): Array { + const queue: Array = []; const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => { - const fnType = getReactFunctionType(fn, pass, environment); - if (fnType === null || ALREADY_COMPILED.has(fn.node)) { + const fnType = getReactFunctionType(fn, pass); + if (fnType === null || programContext.alreadyCompiled.has(fn.node)) { return; } @@ -331,7 +408,7 @@ export function compileProgram( * traversal will loop infinitely. * Ensure we avoid visiting the original function again. */ - ALREADY_COMPILED.add(fn.node); + programContext.alreadyCompiled.add(fn.node); fn.skip(); queue.push({kind: 'original', fn, fnType}); @@ -346,7 +423,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, ClassExpression(node: NodePath) { @@ -355,7 +431,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, FunctionDeclaration: traverseFunction, @@ -370,205 +445,207 @@ export function compileProgram( filename: pass.filename ?? null, }, ); - const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; - const inferredEffectLocations = new Set(); - const processFn = ( - fn: BabelFn, - fnType: ReactFunctionType, - ): null | CodegenFunction => { - let optInDirectives: Array = []; - let optOutDirectives: Array = []; - if (fn.node.body.type === 'BlockStatement') { - optInDirectives = findDirectiveEnablingMemoization( - fn.node.body.directives, - ); - optOutDirectives = findDirectiveDisablingMemoization( - fn.node.body.directives, - ); - } + return queue; +} - /** - * Note that Babel does not attach comment nodes to nodes; they are dangling off of the - * Program node itself. We need to figure out whether an eslint suppression range - * applies to this function first. - */ - const suppressionsInFunction = filterSuppressionsThatAffectFunction( - suppressions, - fn, - ); - let compileResult: - | {kind: 'compile'; compiledFn: CodegenFunction} - | {kind: 'error'; error: unknown}; - if (suppressionsInFunction.length > 0) { - compileResult = { - kind: 'error', - error: suppressionsToCompilerError(suppressionsInFunction), - }; +/** + * Try to compile a source function, taking into account all local suppressions, + * opt-ins, and opt-outs. + * + * Errors encountered during compilation are either logged (if recoverable) or + * thrown (if non-recoverable). + * + * @returns the compiled function or null if the function was skipped (due to + * config settings and/or outputs) + */ +function processFn( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): null | CodegenFunction { + let directives; + if (fn.node.body.type !== 'BlockStatement') { + directives = {optIn: null, optOut: null}; + } else { + directives = { + optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + }; + } + + let compileResult = tryCompileFunction(fn, fnType, programContext); + + if (compileResult.kind === 'error') { + if (directives.optOut != null) { + logError(compileResult.error, programContext, fn.node.loc ?? null); } else { - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'all_features', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - } catch (err) { - compileResult = {kind: 'error', error: err}; - } + handleError(compileResult.error, programContext, fn.node.loc ?? null); } - - if (compileResult.kind === 'error') { - /** - * If an opt out directive is present, log only instead of throwing and don't mark as - * containing a critical error. - */ - if (optOutDirectives.length > 0) { - logError(compileResult.error, pass, fn.node.loc ?? null); - } else { - handleError(compileResult.error, pass, fn.node.loc ?? null); - } - // If non-memoization features are enabled, retry regardless of error kind - if ( - !(environment.enableFire || environment.inferEffectDependencies != null) - ) { - return null; - } - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'no_inferred_memo', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - if ( - !compileResult.compiledFn.hasFireRewrite && - !compileResult.compiledFn.hasInferredEffect - ) { - return null; - } - } catch (err) { - // TODO: we might want to log error here, but this will also result in duplicate logging - if (err instanceof CompilerError) { - retryErrors.push({fn, error: err}); - } - return null; - } - } - - /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. - */ - if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) { - for (const directive of optOutDirectives) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSkip', - fnLoc: fn.node.body.loc ?? null, - reason: `Skipped due to '${directive.value.value}' directive.`, - loc: directive.loc ?? null, - }); - } + const retryResult = retryCompileFunction(fn, fnType, programContext); + if (retryResult == null) { return null; } - - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSuccess', - fnLoc: fn.node.loc ?? null, - fnName: compileResult.compiledFn.id?.name ?? null, - memoSlots: compileResult.compiledFn.memoSlotsUsed, - memoBlocks: compileResult.compiledFn.memoBlocks, - memoValues: compileResult.compiledFn.memoValues, - prunedMemoBlocks: compileResult.compiledFn.prunedMemoBlocks, - prunedMemoValues: compileResult.compiledFn.prunedMemoValues, - }); - - /** - * Always compile functions with opt in directives. - */ - if (optInDirectives.length > 0) { - return compileResult.compiledFn; - } else if (pass.opts.compilationMode === 'annotation') { - /** - * No opt-in directive in annotation mode, so don't insert the compiled function. - */ - return null; - } - - if (!pass.opts.noEmit) { - return compileResult.compiledFn; - } - /** - * inferEffectDependencies + noEmit is currently only used for linting. In - * this mode, add source locations for where the compiler *can* infer effect - * dependencies. - */ - for (const loc of compileResult.compiledFn.inferredEffectLocations) { - if (loc !== GeneratedSource) inferredEffectLocations.add(loc); - } - return null; - }; - - while (queue.length !== 0) { - const current = queue.shift()!; - const compiled = processFn(current.fn, current.fnType); - if (compiled === null) { - continue; - } - for (const outlined of compiled.outlined) { - CompilerError.invariant(outlined.fn.outlined.length === 0, { - reason: 'Unexpected nested outlined functions', - loc: outlined.fn.loc, - }); - const fn = insertNewOutlinedFunctionNode( - program, - current.fn, - outlined.fn, - ); - fn.skip(); - ALREADY_COMPILED.add(fn.node); - if (outlined.type !== null) { - queue.push({ - kind: 'outlined', - fn, - fnType: outlined.type, - }); - } - } - compiledFns.push({ - kind: current.kind, - compiledFn: compiled, - originalFn: current.fn, - }); + compileResult = { + kind: 'compile', + compiledFn: retryResult, + }; } /** - * Do not modify source if there is a module scope level opt out directive. + * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler + * for validation but we don't mutate the babel AST. This allows us to flag if there is an + * unused 'use no forget/memo' directive. */ - const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization( - program.node.directives, - ); - if (moduleScopeOptOutDirectives.length > 0) { + if ( + programContext.opts.ignoreUseNoForget === false && + directives.optOut != null + ) { + programContext.logEvent({ + kind: 'CompileSkip', + fnLoc: fn.node.body.loc ?? null, + reason: `Skipped due to '${directives.optOut.value}' directive.`, + loc: directives.optOut.loc ?? null, + }); return null; } - /* - * Only insert Forget-ified functions if we have not encountered a critical - * error elsewhere in the file, regardless of bailout mode. + const compiledFn = compileResult.compiledFn; + programContext.logEvent({ + kind: 'CompileSuccess', + fnLoc: fn.node.loc ?? null, + fnName: compiledFn.id?.name ?? null, + memoSlots: compiledFn.memoSlotsUsed, + memoBlocks: compiledFn.memoBlocks, + memoValues: compiledFn.memoValues, + prunedMemoBlocks: compiledFn.prunedMemoBlocks, + prunedMemoValues: compiledFn.prunedMemoValues, + }); + + /** + * Always compile functions with opt in directives. */ + if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else if (!programContext.opts.noEmit) { + return compiledFn; + } + + /** + * inferEffectDependencies + noEmit is currently only used for linting. In + * this mode, add source locations for where the compiler *can* infer effect + * dependencies. + */ + for (const loc of compileResult.compiledFn.inferredEffectLocations) { + if (loc !== GeneratedSource) { + programContext.inferredEffectLocations.add(loc); + } + } + return null; +} + +function tryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): + | {kind: 'compile'; compiledFn: CodegenFunction} + | {kind: 'error'; error: unknown} { + /** + * Note that Babel does not attach comment nodes to nodes; they are dangling off of the + * Program node itself. We need to figure out whether an eslint suppression range + * applies to this function first. + */ + const suppressionsInFunction = filterSuppressionsThatAffectFunction( + programContext.suppressions, + fn, + ); + if (suppressionsInFunction.length > 0) { + return { + kind: 'error', + error: suppressionsToCompilerError(suppressionsInFunction), + }; + } + + try { + return { + kind: 'compile', + compiledFn: compileFn( + fn, + programContext.opts.environment, + fnType, + 'all_features', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ), + }; + } catch (err) { + return {kind: 'error', error: err}; + } +} + +/** + * If non-memo feature flags are enabled, retry compilation with a more minimal + * feature set. + * + * @returns a CodegenFunction if retry was successful + */ +function retryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): CodegenFunction | null { + const environment = programContext.opts.environment; + if ( + !(environment.enableFire || environment.inferEffectDependencies != null) + ) { + return null; + } + /** + * Note that function suppressions are not checked in the retry pipeline, as + * they only affect auto-memoization features. + */ + try { + const retryResult = compileFn( + fn, + environment, + fnType, + 'no_inferred_memo', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ); + + if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) { + return null; + } + return retryResult; + } catch (err) { + // TODO: we might want to log error here, but this will also result in duplicate logging + if (err instanceof CompilerError) { + programContext.retryErrors.push({fn, error: err}); + } + return null; + } +} + +/** + * Applies React Compiler generated functions to the babel AST by replacing + * existing functions in place or inserting new declarations. + */ +function applyCompiledFunctions( + program: NodePath, + compiledFns: Array, + pass: CompilerPass, + programContext: ProgramContext, +): void { const referencedBeforeDeclared = pass.opts.gating != null ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) @@ -576,6 +653,7 @@ export function compileProgram( for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); + programContext.alreadyCompiled.add(transformedFn); if (referencedBeforeDeclared != null && kind === 'original') { CompilerError.invariant(pass.opts.gating != null, { @@ -598,7 +676,6 @@ export function compileProgram( if (compiledFns.length > 0) { addImportsToProgram(program, programContext); } - return {retryErrors, inferredEffectLocations}; } function shouldSkipCompilation( @@ -640,14 +717,10 @@ function shouldSkipCompilation( function getReactFunctionType( fn: BabelFn, pass: CompilerPass, - /** - * TODO(mofeiZ): remove once we validate PluginOptions with Zod - */ - environment: EnvironmentConfig, ): ReactFunctionType | null { - const hookPattern = environment.hookPattern; + const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0) + if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 5f6c6986e0..e288c227ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -18,7 +18,7 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {Environment} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; -import {CompileProgramResult} from './Program'; +import {CompileProgramMetadata} from './Program'; function throwInvalidReact( options: Omit, @@ -109,7 +109,7 @@ export default function validateNoUntransformedReferences( filename: string | null, logger: Logger | null, env: EnvironmentConfig, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const moduleLoadChecks = new Map< string, @@ -236,7 +236,7 @@ function transformProgram( moduleLoadChecks: Map>, filename: string | null, logger: Logger | null, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const traversalState: TraversalState = { shouldInvalidateScopes: true, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.expect.md new file mode 100644 index 0000000000..bd70c0138d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo({ propVal }) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.js new file mode 100644 index 0000000000..d1dda06a04 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/retry-no-emit.js @@ -0,0 +1,19 @@ +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md new file mode 100644 index 0000000000..bd70c0138d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo({ propVal }) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js new file mode 100644 index 0000000000..d1dda06a04 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js @@ -0,0 +1,19 @@ +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; From 3f4db9de32da40deb0918951d2284282d3b572fc Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 14:52:44 -0400 Subject: [PATCH 381/422] [compiler][entrypoint] Fix edgecases for noEmit and opt-outs Title --- .../src/Entrypoint/Imports.ts | 4 ++ .../src/Entrypoint/Program.ts | 49 ++++++++++++------- ...bailout-nopanic-shouldnt-outline.expect.md | 3 -- .../compiler/use-memo-noemit.expect.md | 15 +----- 4 files changed, 38 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index d5cac921e9..24ce37cf72 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -59,6 +59,7 @@ type ProgramContextOptions = { opts: PluginOptions; filename: string | null; code: string | null; + hasModuleScopeOptOut: boolean; }; export class ProgramContext { /** @@ -70,6 +71,7 @@ export class ProgramContext { code: string | null; reactRuntimeModule: string; suppressions: Array; + hasModuleScopeOptOut: boolean; /* * This is a hack to work around what seems to be a Babel bug. Babel doesn't @@ -94,6 +96,7 @@ export class ProgramContext { opts, filename, code, + hasModuleScopeOptOut, }: ProgramContextOptions) { this.scope = program.scope; this.opts = opts; @@ -101,6 +104,7 @@ export class ProgramContext { this.code = code; this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); this.suppressions = suppressions; + this.hasModuleScopeOptOut = hasModuleScopeOptOut; } isHookName(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 6cd674e46e..91c6c5ceea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -325,6 +325,8 @@ export function compileProgram( filename: pass.filename, code: pass.code, suppressions, + hasModuleScopeOptOut: + findDirectiveDisablingMemoization(program.node.directives) != null, }); const queue: Array = findFunctionsToCompile( @@ -368,7 +370,19 @@ export function compileProgram( } // Avoid modifying the program if we find a program level opt-out - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } return null; } @@ -520,21 +534,6 @@ function processFn( prunedMemoValues: compiledFn.prunedMemoValues, }); - /** - * Always compile functions with opt in directives. - */ - if (directives.optIn != null) { - return compiledFn; - } else if (programContext.opts.compilationMode === 'annotation') { - /** - * If no opt-in directive is found and the compiler is configured in - * annotation mode, don't insert the compiled function. - */ - return null; - } else if (!programContext.opts.noEmit) { - return compiledFn; - } - /** * inferEffectDependencies + noEmit is currently only used for linting. In * this mode, add source locations for where the compiler *can* infer effect @@ -545,7 +544,23 @@ function processFn( programContext.inferredEffectLocations.add(loc); } } - return null; + + /** + * Always compile functions with opt in directives. + */ + if (programContext.hasModuleScopeOptOut || programContext.opts.noEmit) { + return null; + } else if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else { + return compiledFn; + } } function tryCompileFunction( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md index f24d949205..cfbaa34568 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -20,9 +20,6 @@ function Foo() { function Foo() { return ; } -function _temp() { - return alert("hello!"); -} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md index dfc831555f..c47501945b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -19,22 +19,11 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @noEmit +// @noEmit function Foo() { "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return alert("hello!"); + return ; } export const FIXTURE_ENTRYPOINT = { From 699bcaa9e5b11766373bdf85d03bfbefceddc482 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 14:52:44 -0400 Subject: [PATCH 382/422] [compiler][gating] Experimental directive based gating Adds `dynamicGating` as an experimental option for testing rollout DX at Meta. If specified, this enables dynamic gating which matches `use memo if(...)` directives. #### Example usage Input file ```js // @dynamicGating:{"source":"myModule"} export function MyComponent() { 'use memo if(isEnabled)'; return
...
; } ``` Compiler output ```js import {isEnabled} from 'myModule'; export const MyComponent = isEnabled() ? : ; ``` --- .../src/Entrypoint/Options.ts | 46 ++++++ .../src/Entrypoint/Program.ts | 143 +++++++++++++++--- .../dynamic-gating-annotation.expect.md | 50 ++++++ .../gating/dynamic-gating-annotation.js | 11 ++ .../dynamic-gating-bailout-nopanic.expect.md | 66 ++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 ++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 ++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ .../dynamic-gating-invalid-multiple.expect.md | 45 ++++++ .../gating/dynamic-gating-invalid-multiple.js | 12 ++ .../gating/dynamic-gating-noemit.expect.md | 37 +++++ .../compiler/gating/dynamic-gating-noemit.js | 11 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 ++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 + 22 files changed, 692 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..96cce887d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,28 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + * ``` + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +270,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +319,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 91c6c5ceea..8449b40ca1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,64 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else if (result.length > 1) { + const error = new CompilerError(); + error.push({ + reason: `Multiple dynamic gating directives found`, + description: `Expected a single directive but found [${result + .map(r => r.directive.value.value) + .join(', ')}]`, + severity: ErrorSeverity.InvalidReact, + loc: result[0].directive.loc ?? null, + suggestions: null, + }); + return Err(error); + } else if (result.length === 1) { + return Ok({ + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + }); + } else { + return Ok(null); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -477,12 +545,32 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we handle + * the error and fall back to the safest option which is to not optimize + * the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -661,25 +749,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -735,8 +829,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.unwrapOr(null) != null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md new file mode 100644 index 0000000000..364239e4e3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js new file mode 100644 index 0000000000..c30b30fe6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md new file mode 100644 index 0000000000..327adbe792 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + "use memo if(getTrue)"; + "use memo if(getFalse)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}} +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js new file mode 100644 index 0000000000..867ac8ee34 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js @@ -0,0 +1,12 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md new file mode 100644 index 0000000000..81ebd6dd9f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + "use memo if(getTrue)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js new file mode 100644 index 0000000000..97cf777a55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From 64f7bddc66f502758e2166ca45ea5f224562e810 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 14:54:24 -0400 Subject: [PATCH 383/422] [compiler][entrypoint] Fix edgecases for noEmit and opt-outs Title --- .../src/Entrypoint/Imports.ts | 4 ++ .../src/Entrypoint/Program.ts | 49 ++++++++++++------- ...bailout-nopanic-shouldnt-outline.expect.md | 3 -- .../compiler/use-memo-noemit.expect.md | 15 +----- 4 files changed, 38 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index d5cac921e9..24ce37cf72 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -59,6 +59,7 @@ type ProgramContextOptions = { opts: PluginOptions; filename: string | null; code: string | null; + hasModuleScopeOptOut: boolean; }; export class ProgramContext { /** @@ -70,6 +71,7 @@ export class ProgramContext { code: string | null; reactRuntimeModule: string; suppressions: Array; + hasModuleScopeOptOut: boolean; /* * This is a hack to work around what seems to be a Babel bug. Babel doesn't @@ -94,6 +96,7 @@ export class ProgramContext { opts, filename, code, + hasModuleScopeOptOut, }: ProgramContextOptions) { this.scope = program.scope; this.opts = opts; @@ -101,6 +104,7 @@ export class ProgramContext { this.code = code; this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); this.suppressions = suppressions; + this.hasModuleScopeOptOut = hasModuleScopeOptOut; } isHookName(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 6cd674e46e..91c6c5ceea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -325,6 +325,8 @@ export function compileProgram( filename: pass.filename, code: pass.code, suppressions, + hasModuleScopeOptOut: + findDirectiveDisablingMemoization(program.node.directives) != null, }); const queue: Array = findFunctionsToCompile( @@ -368,7 +370,19 @@ export function compileProgram( } // Avoid modifying the program if we find a program level opt-out - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } return null; } @@ -520,21 +534,6 @@ function processFn( prunedMemoValues: compiledFn.prunedMemoValues, }); - /** - * Always compile functions with opt in directives. - */ - if (directives.optIn != null) { - return compiledFn; - } else if (programContext.opts.compilationMode === 'annotation') { - /** - * If no opt-in directive is found and the compiler is configured in - * annotation mode, don't insert the compiled function. - */ - return null; - } else if (!programContext.opts.noEmit) { - return compiledFn; - } - /** * inferEffectDependencies + noEmit is currently only used for linting. In * this mode, add source locations for where the compiler *can* infer effect @@ -545,7 +544,23 @@ function processFn( programContext.inferredEffectLocations.add(loc); } } - return null; + + /** + * Always compile functions with opt in directives. + */ + if (programContext.hasModuleScopeOptOut || programContext.opts.noEmit) { + return null; + } else if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else { + return compiledFn; + } } function tryCompileFunction( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md index f24d949205..cfbaa34568 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -20,9 +20,6 @@ function Foo() { function Foo() { return ; } -function _temp() { - return alert("hello!"); -} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md index dfc831555f..c47501945b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -19,22 +19,11 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @noEmit +// @noEmit function Foo() { "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return alert("hello!"); + return ; } export const FIXTURE_ENTRYPOINT = { From 600239220d9b23e018926e0dd494cc6f8ed2b5f5 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 14:52:42 -0400 Subject: [PATCH 384/422] [compiler][be] Make program traversal more readable React Compiler's program traversal logic is pretty lengthy and complex as we've added a lot of features piecemeal. `compileProgram` is 300+ lines long and has confusing control flow (defining helpers inline, invoking visitors, mutating-asts-while-iterating, mutating global `ALREADY_COMPILED` state). - Moved more stuff to `ProgramContext` - Separated `compileProgram` into a bunch of helpers Will come back and add more comments inline --- .../src/Entrypoint/Imports.ts | 62 +- .../src/Entrypoint/Program.ts | 567 ++++++++++-------- .../ValidateNoUntransformedReferences.ts | 6 +- .../no-emit-lint-repro.expect.md | 0 .../{ => no-emit}/no-emit-lint-repro.js | 0 .../no-emit/retry-no-emit.expect.md | 64 ++ .../no-emit/retry-no-emit.js | 19 + 7 files changed, 455 insertions(+), 263 deletions(-) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/{ => no-emit}/no-emit-lint-repro.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/{ => no-emit}/no-emit-lint-repro.js (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 704f696b3b..d5cac921e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -18,8 +18,9 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; -import {CompilerReactTarget} from './Options'; -import {getReactCompilerRuntimeModule} from './Program'; +import {LoggerEvent, PluginOptions} from './Options'; +import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( path: NodePath, @@ -52,32 +53,61 @@ export function validateRestrictedImports( } } +type ProgramContextOptions = { + program: NodePath; + suppressions: Array; + opts: PluginOptions; + filename: string | null; + code: string | null; +}; export class ProgramContext { - /* Program and environment context */ + /** + * Program and environment context + */ scope: BabelScope; + opts: PluginOptions; + filename: string | null; + code: string | null; reactRuntimeModule: string; - hookPattern: string | null; + suppressions: Array; + /* + * This is a hack to work around what seems to be a Babel bug. Babel doesn't + * consistently respect the `skip()` function to avoid revisiting a node within + * a pass, so we use this set to track nodes that we have compiled. + */ + alreadyCompiled: WeakSet | Set = new (WeakSet ?? Set)(); // known generated or referenced identifiers in the program knownReferencedNames: Set = new Set(); // generated imports imports: Map> = new Map(); - constructor( - program: NodePath, - reactRuntimeModule: CompilerReactTarget, - hookPattern: string | null, - ) { - this.hookPattern = hookPattern; + /** + * Metadata from compilation + */ + retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; + inferredEffectLocations: Set = new Set(); + + constructor({ + program, + suppressions, + opts, + filename, + code, + }: ProgramContextOptions) { this.scope = program.scope; - this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule); + this.opts = opts; + this.filename = filename; + this.code = code; + this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); + this.suppressions = suppressions; } isHookName(name: string): boolean { - if (this.hookPattern == null) { + if (this.opts.environment.hookPattern == null) { return isHookName(name); } else { - const match = new RegExp(this.hookPattern).exec(name); + const match = new RegExp(this.opts.environment.hookPattern).exec(name); return ( match != null && typeof match[1] === 'string' && isHookName(match[1]) ); @@ -179,6 +209,12 @@ export class ProgramContext { }); return Err(error); } + + logEvent(event: LoggerEvent): void { + if (this.opts.logger != null) { + this.opts.logger.logEvent(this.filename, event); + } + } } function getExistingImports( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 34347f10b8..6cd674e46e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment'; +import {ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -43,17 +43,21 @@ export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); export function findDirectiveEnablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), + ) ?? null ); } export function findDirectiveDisablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_OUT_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_OUT_DIRECTIVES.has(directive.value.value), + ) ?? null ); } @@ -88,13 +92,16 @@ export type CompileResult = { function logError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - if (pass.opts.logger) { + if (context.opts.logger) { if (err instanceof CompilerError) { for (const detail of err.details) { - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, detail: detail.options, @@ -108,7 +115,7 @@ function logError( stringifiedError = err?.toString() ?? '[ null ]'; } - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'PipelineError', fnLoc, data: stringifiedError, @@ -118,13 +125,17 @@ function logError( } function handleError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - logError(err, pass, fnLoc); + logError(err, context, fnLoc); if ( - pass.opts.panicThreshold === 'all_errors' || - (pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) || + context.opts.panicThreshold === 'all_errors' || + (context.opts.panicThreshold === 'critical_errors' && + isCriticalError(err)) || isConfigError(err) // Always throws regardless of panic threshold ) { throw err; @@ -187,7 +198,6 @@ export function createNewFunctionNode( } } // Avoid visiting the new transformed version - ALREADY_COMPILED.add(transformedFn); return transformedFn; } @@ -239,13 +249,6 @@ function insertNewOutlinedFunctionNode( } } -/* - * This is a hack to work around what seems to be a Babel bug. Babel doesn't - * consistently respect the `skip()` function to avoid revisiting a node within - * a pass, so we use this set to track nodes that we have compiled. - */ -const ALREADY_COMPILED: WeakSet | Set = new (WeakSet ?? Set)(); - const DEFAULT_ESLINT_SUPPRESSIONS = [ 'react-hooks/exhaustive-deps', 'react-hooks/rules-of-hooks', @@ -268,41 +271,43 @@ function isFilePartOfSources( return false; } -export type CompileProgramResult = { +export type CompileProgramMetadata = { retryErrors: Array<{fn: BabelFn; error: CompilerError}>; inferredEffectLocations: Set; }; /** - * `compileProgram` is directly invoked by the react-compiler babel plugin, so - * exceptions thrown by this function will fail the babel build. - * - call `handleError` if your error is recoverable. - * Unless the error is a warning / info diagnostic, compilation of a function - * / entire file should also be skipped. - * - throw an exception if the error is fatal / not recoverable. - * Examples of this are invalid compiler configs or failure to codegen outlined - * functions *after* already emitting optimized components / hooks that invoke - * the outlined functions. + * Main entrypoint for React Compiler. + * + * @param program The Babel program node to compile + * @param pass Compiler configuration and context + * @returns Compilation results or null if compilation was skipped */ export function compileProgram( program: NodePath, pass: CompilerPass, -): CompileProgramResult | null { +): CompileProgramMetadata | null { + /** + * This is directly invoked by the react-compiler babel plugin, so exceptions + * thrown by this function will fail the babel build. + * - call `handleError` if your error is recoverable. + * Unless the error is a warning / info diagnostic, compilation of a function + * / entire file should also be skipped. + * - throw an exception if the error is fatal / not recoverable. + * Examples of this are invalid compiler configs or failure to codegen outlined + * functions *after* already emitting optimized components / hooks that invoke + * the outlined functions. + */ if (shouldSkipCompilation(program, pass)) { return null; } - - const environment = pass.opts.environment; - const restrictedImportsErr = validateRestrictedImports(program, environment); + const restrictedImportsErr = validateRestrictedImports( + program, + pass.opts.environment, + ); if (restrictedImportsErr) { handleError(restrictedImportsErr, pass, null); return null; } - - const programContext = new ProgramContext( - program, - pass.opts.target, - environment.hookPattern, - ); /* * Record lint errors and critical errors as depending on Forget's config, * we may still need to run Forget's analysis on every function (even if we @@ -313,16 +318,88 @@ export function compileProgram( pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions, ); - const queue: Array<{ - kind: 'original' | 'outlined'; - fn: BabelFn; - fnType: ReactFunctionType; - }> = []; + + const programContext = new ProgramContext({ + program: program, + opts: pass.opts, + filename: pass.filename, + code: pass.code, + suppressions, + }); + + const queue: Array = findFunctionsToCompile( + program, + pass, + programContext, + ); const compiledFns: Array = []; + while (queue.length !== 0) { + const current = queue.shift()!; + const compiled = processFn(current.fn, current.fnType, programContext); + + if (compiled != null) { + for (const outlined of compiled.outlined) { + CompilerError.invariant(outlined.fn.outlined.length === 0, { + reason: 'Unexpected nested outlined functions', + loc: outlined.fn.loc, + }); + const fn = insertNewOutlinedFunctionNode( + program, + current.fn, + outlined.fn, + ); + fn.skip(); + programContext.alreadyCompiled.add(fn.node); + if (outlined.type !== null) { + queue.push({ + kind: 'outlined', + fn, + fnType: outlined.type, + }); + } + } + compiledFns.push({ + kind: current.kind, + originalFn: current.fn, + compiledFn: compiled, + }); + } + } + + // Avoid modifying the program if we find a program level opt-out + if (findDirectiveDisablingMemoization(program.node.directives) != null) { + return null; + } + + // Insert React Compiler generated functions into the Babel AST + applyCompiledFunctions(program, compiledFns, pass, programContext); + + return { + retryErrors: programContext.retryErrors, + inferredEffectLocations: programContext.inferredEffectLocations, + }; +} + +type CompileSource = { + kind: 'original' | 'outlined'; + fn: BabelFn; + fnType: ReactFunctionType; +}; +/** + * Find all React components and hooks that need to be compiled + * + * @returns An array of React functions from @param program to transform + */ +function findFunctionsToCompile( + program: NodePath, + pass: CompilerPass, + programContext: ProgramContext, +): Array { + const queue: Array = []; const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => { - const fnType = getReactFunctionType(fn, pass, environment); - if (fnType === null || ALREADY_COMPILED.has(fn.node)) { + const fnType = getReactFunctionType(fn, pass); + if (fnType === null || programContext.alreadyCompiled.has(fn.node)) { return; } @@ -331,7 +408,7 @@ export function compileProgram( * traversal will loop infinitely. * Ensure we avoid visiting the original function again. */ - ALREADY_COMPILED.add(fn.node); + programContext.alreadyCompiled.add(fn.node); fn.skip(); queue.push({kind: 'original', fn, fnType}); @@ -346,7 +423,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, ClassExpression(node: NodePath) { @@ -355,7 +431,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, FunctionDeclaration: traverseFunction, @@ -370,205 +445,207 @@ export function compileProgram( filename: pass.filename ?? null, }, ); - const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; - const inferredEffectLocations = new Set(); - const processFn = ( - fn: BabelFn, - fnType: ReactFunctionType, - ): null | CodegenFunction => { - let optInDirectives: Array = []; - let optOutDirectives: Array = []; - if (fn.node.body.type === 'BlockStatement') { - optInDirectives = findDirectiveEnablingMemoization( - fn.node.body.directives, - ); - optOutDirectives = findDirectiveDisablingMemoization( - fn.node.body.directives, - ); - } + return queue; +} - /** - * Note that Babel does not attach comment nodes to nodes; they are dangling off of the - * Program node itself. We need to figure out whether an eslint suppression range - * applies to this function first. - */ - const suppressionsInFunction = filterSuppressionsThatAffectFunction( - suppressions, - fn, - ); - let compileResult: - | {kind: 'compile'; compiledFn: CodegenFunction} - | {kind: 'error'; error: unknown}; - if (suppressionsInFunction.length > 0) { - compileResult = { - kind: 'error', - error: suppressionsToCompilerError(suppressionsInFunction), - }; +/** + * Try to compile a source function, taking into account all local suppressions, + * opt-ins, and opt-outs. + * + * Errors encountered during compilation are either logged (if recoverable) or + * thrown (if non-recoverable). + * + * @returns the compiled function or null if the function was skipped (due to + * config settings and/or outputs) + */ +function processFn( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): null | CodegenFunction { + let directives; + if (fn.node.body.type !== 'BlockStatement') { + directives = {optIn: null, optOut: null}; + } else { + directives = { + optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + }; + } + + let compileResult = tryCompileFunction(fn, fnType, programContext); + + if (compileResult.kind === 'error') { + if (directives.optOut != null) { + logError(compileResult.error, programContext, fn.node.loc ?? null); } else { - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'all_features', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - } catch (err) { - compileResult = {kind: 'error', error: err}; - } + handleError(compileResult.error, programContext, fn.node.loc ?? null); } - - if (compileResult.kind === 'error') { - /** - * If an opt out directive is present, log only instead of throwing and don't mark as - * containing a critical error. - */ - if (optOutDirectives.length > 0) { - logError(compileResult.error, pass, fn.node.loc ?? null); - } else { - handleError(compileResult.error, pass, fn.node.loc ?? null); - } - // If non-memoization features are enabled, retry regardless of error kind - if ( - !(environment.enableFire || environment.inferEffectDependencies != null) - ) { - return null; - } - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'no_inferred_memo', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - if ( - !compileResult.compiledFn.hasFireRewrite && - !compileResult.compiledFn.hasInferredEffect - ) { - return null; - } - } catch (err) { - // TODO: we might want to log error here, but this will also result in duplicate logging - if (err instanceof CompilerError) { - retryErrors.push({fn, error: err}); - } - return null; - } - } - - /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. - */ - if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) { - for (const directive of optOutDirectives) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSkip', - fnLoc: fn.node.body.loc ?? null, - reason: `Skipped due to '${directive.value.value}' directive.`, - loc: directive.loc ?? null, - }); - } + const retryResult = retryCompileFunction(fn, fnType, programContext); + if (retryResult == null) { return null; } - - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSuccess', - fnLoc: fn.node.loc ?? null, - fnName: compileResult.compiledFn.id?.name ?? null, - memoSlots: compileResult.compiledFn.memoSlotsUsed, - memoBlocks: compileResult.compiledFn.memoBlocks, - memoValues: compileResult.compiledFn.memoValues, - prunedMemoBlocks: compileResult.compiledFn.prunedMemoBlocks, - prunedMemoValues: compileResult.compiledFn.prunedMemoValues, - }); - - /** - * Always compile functions with opt in directives. - */ - if (optInDirectives.length > 0) { - return compileResult.compiledFn; - } else if (pass.opts.compilationMode === 'annotation') { - /** - * No opt-in directive in annotation mode, so don't insert the compiled function. - */ - return null; - } - - if (!pass.opts.noEmit) { - return compileResult.compiledFn; - } - /** - * inferEffectDependencies + noEmit is currently only used for linting. In - * this mode, add source locations for where the compiler *can* infer effect - * dependencies. - */ - for (const loc of compileResult.compiledFn.inferredEffectLocations) { - if (loc !== GeneratedSource) inferredEffectLocations.add(loc); - } - return null; - }; - - while (queue.length !== 0) { - const current = queue.shift()!; - const compiled = processFn(current.fn, current.fnType); - if (compiled === null) { - continue; - } - for (const outlined of compiled.outlined) { - CompilerError.invariant(outlined.fn.outlined.length === 0, { - reason: 'Unexpected nested outlined functions', - loc: outlined.fn.loc, - }); - const fn = insertNewOutlinedFunctionNode( - program, - current.fn, - outlined.fn, - ); - fn.skip(); - ALREADY_COMPILED.add(fn.node); - if (outlined.type !== null) { - queue.push({ - kind: 'outlined', - fn, - fnType: outlined.type, - }); - } - } - compiledFns.push({ - kind: current.kind, - compiledFn: compiled, - originalFn: current.fn, - }); + compileResult = { + kind: 'compile', + compiledFn: retryResult, + }; } /** - * Do not modify source if there is a module scope level opt out directive. + * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler + * for validation but we don't mutate the babel AST. This allows us to flag if there is an + * unused 'use no forget/memo' directive. */ - const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization( - program.node.directives, - ); - if (moduleScopeOptOutDirectives.length > 0) { + if ( + programContext.opts.ignoreUseNoForget === false && + directives.optOut != null + ) { + programContext.logEvent({ + kind: 'CompileSkip', + fnLoc: fn.node.body.loc ?? null, + reason: `Skipped due to '${directives.optOut.value}' directive.`, + loc: directives.optOut.loc ?? null, + }); return null; } - /* - * Only insert Forget-ified functions if we have not encountered a critical - * error elsewhere in the file, regardless of bailout mode. + const compiledFn = compileResult.compiledFn; + programContext.logEvent({ + kind: 'CompileSuccess', + fnLoc: fn.node.loc ?? null, + fnName: compiledFn.id?.name ?? null, + memoSlots: compiledFn.memoSlotsUsed, + memoBlocks: compiledFn.memoBlocks, + memoValues: compiledFn.memoValues, + prunedMemoBlocks: compiledFn.prunedMemoBlocks, + prunedMemoValues: compiledFn.prunedMemoValues, + }); + + /** + * Always compile functions with opt in directives. */ + if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else if (!programContext.opts.noEmit) { + return compiledFn; + } + + /** + * inferEffectDependencies + noEmit is currently only used for linting. In + * this mode, add source locations for where the compiler *can* infer effect + * dependencies. + */ + for (const loc of compileResult.compiledFn.inferredEffectLocations) { + if (loc !== GeneratedSource) { + programContext.inferredEffectLocations.add(loc); + } + } + return null; +} + +function tryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): + | {kind: 'compile'; compiledFn: CodegenFunction} + | {kind: 'error'; error: unknown} { + /** + * Note that Babel does not attach comment nodes to nodes; they are dangling off of the + * Program node itself. We need to figure out whether an eslint suppression range + * applies to this function first. + */ + const suppressionsInFunction = filterSuppressionsThatAffectFunction( + programContext.suppressions, + fn, + ); + if (suppressionsInFunction.length > 0) { + return { + kind: 'error', + error: suppressionsToCompilerError(suppressionsInFunction), + }; + } + + try { + return { + kind: 'compile', + compiledFn: compileFn( + fn, + programContext.opts.environment, + fnType, + 'all_features', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ), + }; + } catch (err) { + return {kind: 'error', error: err}; + } +} + +/** + * If non-memo feature flags are enabled, retry compilation with a more minimal + * feature set. + * + * @returns a CodegenFunction if retry was successful + */ +function retryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): CodegenFunction | null { + const environment = programContext.opts.environment; + if ( + !(environment.enableFire || environment.inferEffectDependencies != null) + ) { + return null; + } + /** + * Note that function suppressions are not checked in the retry pipeline, as + * they only affect auto-memoization features. + */ + try { + const retryResult = compileFn( + fn, + environment, + fnType, + 'no_inferred_memo', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ); + + if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) { + return null; + } + return retryResult; + } catch (err) { + // TODO: we might want to log error here, but this will also result in duplicate logging + if (err instanceof CompilerError) { + programContext.retryErrors.push({fn, error: err}); + } + return null; + } +} + +/** + * Applies React Compiler generated functions to the babel AST by replacing + * existing functions in place or inserting new declarations. + */ +function applyCompiledFunctions( + program: NodePath, + compiledFns: Array, + pass: CompilerPass, + programContext: ProgramContext, +): void { const referencedBeforeDeclared = pass.opts.gating != null ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) @@ -576,6 +653,7 @@ export function compileProgram( for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); + programContext.alreadyCompiled.add(transformedFn); if (referencedBeforeDeclared != null && kind === 'original') { CompilerError.invariant(pass.opts.gating != null, { @@ -598,7 +676,6 @@ export function compileProgram( if (compiledFns.length > 0) { addImportsToProgram(program, programContext); } - return {retryErrors, inferredEffectLocations}; } function shouldSkipCompilation( @@ -640,14 +717,10 @@ function shouldSkipCompilation( function getReactFunctionType( fn: BabelFn, pass: CompilerPass, - /** - * TODO(mofeiZ): remove once we validate PluginOptions with Zod - */ - environment: EnvironmentConfig, ): ReactFunctionType | null { - const hookPattern = environment.hookPattern; + const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0) + if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 5f6c6986e0..e288c227ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -18,7 +18,7 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {Environment} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; -import {CompileProgramResult} from './Program'; +import {CompileProgramMetadata} from './Program'; function throwInvalidReact( options: Omit, @@ -109,7 +109,7 @@ export default function validateNoUntransformedReferences( filename: string | null, logger: Logger | null, env: EnvironmentConfig, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const moduleLoadChecks = new Map< string, @@ -236,7 +236,7 @@ function transformProgram( moduleLoadChecks: Map>, filename: string | null, logger: Logger | null, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const traversalState: TraversalState = { shouldInvalidateScopes: true, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md new file mode 100644 index 0000000000..bd70c0138d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo({ propVal }) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js new file mode 100644 index 0000000000..d1dda06a04 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js @@ -0,0 +1,19 @@ +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; From 66a3d58315473ef8614259cbb729618223bbc431 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 14:54:24 -0400 Subject: [PATCH 385/422] [compiler][gating] Experimental directive based gating Adds `dynamicGating` as an experimental option for testing rollout DX at Meta. If specified, this enables dynamic gating which matches `use memo if(...)` directives. #### Example usage Input file ```js // @dynamicGating:{"source":"myModule"} export function MyComponent() { 'use memo if(isEnabled)'; return
...
; } ``` Compiler output ```js import {isEnabled} from 'myModule'; export const MyComponent = isEnabled() ? : ; ``` --- .../src/Entrypoint/Options.ts | 46 ++++++ .../src/Entrypoint/Program.ts | 143 +++++++++++++++--- .../dynamic-gating-annotation.expect.md | 50 ++++++ .../gating/dynamic-gating-annotation.js | 11 ++ .../dynamic-gating-bailout-nopanic.expect.md | 66 ++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 ++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 ++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ .../dynamic-gating-invalid-multiple.expect.md | 45 ++++++ .../gating/dynamic-gating-invalid-multiple.js | 12 ++ .../gating/dynamic-gating-noemit.expect.md | 37 +++++ .../compiler/gating/dynamic-gating-noemit.js | 11 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 ++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 + 22 files changed, 692 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..96cce887d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,28 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + * ``` + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +270,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +319,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 91c6c5ceea..8449b40ca1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,64 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else if (result.length > 1) { + const error = new CompilerError(); + error.push({ + reason: `Multiple dynamic gating directives found`, + description: `Expected a single directive but found [${result + .map(r => r.directive.value.value) + .join(', ')}]`, + severity: ErrorSeverity.InvalidReact, + loc: result[0].directive.loc ?? null, + suggestions: null, + }); + return Err(error); + } else if (result.length === 1) { + return Ok({ + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + }); + } else { + return Ok(null); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -477,12 +545,32 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we handle + * the error and fall back to the safest option which is to not optimize + * the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -661,25 +749,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -735,8 +829,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.unwrapOr(null) != null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md new file mode 100644 index 0000000000..364239e4e3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js new file mode 100644 index 0000000000..c30b30fe6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md new file mode 100644 index 0000000000..327adbe792 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + "use memo if(getTrue)"; + "use memo if(getFalse)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}} +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js new file mode 100644 index 0000000000..867ac8ee34 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js @@ -0,0 +1,12 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md new file mode 100644 index 0000000000..81ebd6dd9f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + "use memo if(getTrue)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js new file mode 100644 index 0000000000..97cf777a55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From bb0807a7257c3462ae91274110764c086542912e Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 14:57:31 -0400 Subject: [PATCH 386/422] [compiler][entrypoint] Fix edgecases for noEmit and opt-outs Title --- .../src/Entrypoint/Imports.ts | 4 ++ .../src/Entrypoint/Program.ts | 46 ++++++++++++------- ...bailout-nopanic-shouldnt-outline.expect.md | 3 -- .../compiler/use-memo-noemit.expect.md | 15 +----- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index d5cac921e9..24ce37cf72 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -59,6 +59,7 @@ type ProgramContextOptions = { opts: PluginOptions; filename: string | null; code: string | null; + hasModuleScopeOptOut: boolean; }; export class ProgramContext { /** @@ -70,6 +71,7 @@ export class ProgramContext { code: string | null; reactRuntimeModule: string; suppressions: Array; + hasModuleScopeOptOut: boolean; /* * This is a hack to work around what seems to be a Babel bug. Babel doesn't @@ -94,6 +96,7 @@ export class ProgramContext { opts, filename, code, + hasModuleScopeOptOut, }: ProgramContextOptions) { this.scope = program.scope; this.opts = opts; @@ -101,6 +104,7 @@ export class ProgramContext { this.code = code; this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); this.suppressions = suppressions; + this.hasModuleScopeOptOut = hasModuleScopeOptOut; } isHookName(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 6cd674e46e..68ade519b7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -325,6 +325,8 @@ export function compileProgram( filename: pass.filename, code: pass.code, suppressions, + hasModuleScopeOptOut: + findDirectiveDisablingMemoization(program.node.directives) != null, }); const queue: Array = findFunctionsToCompile( @@ -368,7 +370,19 @@ export function compileProgram( } // Avoid modifying the program if we find a program level opt-out - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } return null; } @@ -520,21 +534,6 @@ function processFn( prunedMemoValues: compiledFn.prunedMemoValues, }); - /** - * Always compile functions with opt in directives. - */ - if (directives.optIn != null) { - return compiledFn; - } else if (programContext.opts.compilationMode === 'annotation') { - /** - * If no opt-in directive is found and the compiler is configured in - * annotation mode, don't insert the compiled function. - */ - return null; - } else if (!programContext.opts.noEmit) { - return compiledFn; - } - /** * inferEffectDependencies + noEmit is currently only used for linting. In * this mode, add source locations for where the compiler *can* infer effect @@ -545,7 +544,20 @@ function processFn( programContext.inferredEffectLocations.add(loc); } } - return null; + + if (programContext.hasModuleScopeOptOut || programContext.opts.noEmit) { + return null; + } else if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else { + return compiledFn; + } } function tryCompileFunction( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md index f24d949205..cfbaa34568 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -20,9 +20,6 @@ function Foo() { function Foo() { return ; } -function _temp() { - return alert("hello!"); -} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md index dfc831555f..c47501945b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -19,22 +19,11 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @noEmit +// @noEmit function Foo() { "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return alert("hello!"); + return ; } export const FIXTURE_ENTRYPOINT = { From 0013cccdaa86a874c63abb429ca08605e543bdc1 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 14:57:32 -0400 Subject: [PATCH 387/422] [compiler][gating] Experimental directive based gating Adds `dynamicGating` as an experimental option for testing rollout DX at Meta. If specified, this enables dynamic gating which matches `use memo if(...)` directives. #### Example usage Input file ```js // @dynamicGating:{"source":"myModule"} export function MyComponent() { 'use memo if(isEnabled)'; return
...
; } ``` Compiler output ```js import {isEnabled} from 'myModule'; export const MyComponent = isEnabled() ? : ; ``` --- .../src/Entrypoint/Options.ts | 46 ++++++ .../src/Entrypoint/Program.ts | 143 +++++++++++++++--- .../dynamic-gating-annotation.expect.md | 50 ++++++ .../gating/dynamic-gating-annotation.js | 11 ++ .../dynamic-gating-bailout-nopanic.expect.md | 66 ++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 ++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 ++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ .../dynamic-gating-invalid-multiple.expect.md | 45 ++++++ .../gating/dynamic-gating-invalid-multiple.js | 12 ++ .../gating/dynamic-gating-noemit.expect.md | 37 +++++ .../compiler/gating/dynamic-gating-noemit.js | 11 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 ++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 + 22 files changed, 692 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..96cce887d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,28 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + * ``` + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +270,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +319,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 68ade519b7..70b01d3a44 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,64 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else if (result.length > 1) { + const error = new CompilerError(); + error.push({ + reason: `Multiple dynamic gating directives found`, + description: `Expected a single directive but found [${result + .map(r => r.directive.value.value) + .join(', ')}]`, + severity: ErrorSeverity.InvalidReact, + loc: result[0].directive.loc ?? null, + suggestions: null, + }); + return Err(error); + } else if (result.length === 1) { + return Ok({ + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + }); + } else { + return Ok(null); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -477,12 +545,32 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we handle + * the error and fall back to the safest option which is to not optimize + * the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -658,25 +746,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -732,8 +826,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.unwrapOr(null) != null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md new file mode 100644 index 0000000000..364239e4e3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js new file mode 100644 index 0000000000..c30b30fe6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md new file mode 100644 index 0000000000..327adbe792 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + "use memo if(getTrue)"; + "use memo if(getFalse)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}} +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js new file mode 100644 index 0000000000..867ac8ee34 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js @@ -0,0 +1,12 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md new file mode 100644 index 0000000000..81ebd6dd9f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + "use memo if(getTrue)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js new file mode 100644 index 0000000000..97cf777a55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From 1c521f5031c147ef224e610169d0ab3c4d37867b Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 15:44:29 -0400 Subject: [PATCH 388/422] [compiler][be] Make program traversal more readable React Compiler's program traversal logic is pretty lengthy and complex as we've added a lot of features piecemeal. `compileProgram` is 300+ lines long and has confusing control flow (defining helpers inline, invoking visitors, mutating-asts-while-iterating, mutating global `ALREADY_COMPILED` state). - Moved more stuff to `ProgramContext` - Separated `compileProgram` into a bunch of helpers Will come back and add more comments inline --- .../src/Entrypoint/Imports.ts | 62 +- .../src/Entrypoint/Program.ts | 545 ++++++++++-------- .../ValidateNoUntransformedReferences.ts | 6 +- .../no-emit-lint-repro.expect.md | 0 .../{ => no-emit}/no-emit-lint-repro.js | 0 .../no-emit/retry-no-emit.expect.md | 64 ++ .../no-emit/retry-no-emit.js | 19 + .../no-emit/retry-opt-in--no-emit.expect.md | 60 ++ .../no-emit/retry-opt-in--no-emit.js | 21 + 9 files changed, 524 insertions(+), 253 deletions(-) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/{ => no-emit}/no-emit-lint-repro.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/{ => no-emit}/no-emit-lint-repro.js (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 704f696b3b..d5cac921e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -18,8 +18,9 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; -import {CompilerReactTarget} from './Options'; -import {getReactCompilerRuntimeModule} from './Program'; +import {LoggerEvent, PluginOptions} from './Options'; +import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( path: NodePath, @@ -52,32 +53,61 @@ export function validateRestrictedImports( } } +type ProgramContextOptions = { + program: NodePath; + suppressions: Array; + opts: PluginOptions; + filename: string | null; + code: string | null; +}; export class ProgramContext { - /* Program and environment context */ + /** + * Program and environment context + */ scope: BabelScope; + opts: PluginOptions; + filename: string | null; + code: string | null; reactRuntimeModule: string; - hookPattern: string | null; + suppressions: Array; + /* + * This is a hack to work around what seems to be a Babel bug. Babel doesn't + * consistently respect the `skip()` function to avoid revisiting a node within + * a pass, so we use this set to track nodes that we have compiled. + */ + alreadyCompiled: WeakSet | Set = new (WeakSet ?? Set)(); // known generated or referenced identifiers in the program knownReferencedNames: Set = new Set(); // generated imports imports: Map> = new Map(); - constructor( - program: NodePath, - reactRuntimeModule: CompilerReactTarget, - hookPattern: string | null, - ) { - this.hookPattern = hookPattern; + /** + * Metadata from compilation + */ + retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; + inferredEffectLocations: Set = new Set(); + + constructor({ + program, + suppressions, + opts, + filename, + code, + }: ProgramContextOptions) { this.scope = program.scope; - this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule); + this.opts = opts; + this.filename = filename; + this.code = code; + this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); + this.suppressions = suppressions; } isHookName(name: string): boolean { - if (this.hookPattern == null) { + if (this.opts.environment.hookPattern == null) { return isHookName(name); } else { - const match = new RegExp(this.hookPattern).exec(name); + const match = new RegExp(this.opts.environment.hookPattern).exec(name); return ( match != null && typeof match[1] === 'string' && isHookName(match[1]) ); @@ -179,6 +209,12 @@ export class ProgramContext { }); return Err(error); } + + logEvent(event: LoggerEvent): void { + if (this.opts.logger != null) { + this.opts.logger.logEvent(this.filename, event); + } + } } function getExistingImports( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 34347f10b8..1a3445531d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment'; +import {ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -43,17 +43,21 @@ export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); export function findDirectiveEnablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), + ) ?? null ); } export function findDirectiveDisablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_OUT_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_OUT_DIRECTIVES.has(directive.value.value), + ) ?? null ); } @@ -88,13 +92,16 @@ export type CompileResult = { function logError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - if (pass.opts.logger) { + if (context.opts.logger) { if (err instanceof CompilerError) { for (const detail of err.details) { - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, detail: detail.options, @@ -108,7 +115,7 @@ function logError( stringifiedError = err?.toString() ?? '[ null ]'; } - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'PipelineError', fnLoc, data: stringifiedError, @@ -118,13 +125,17 @@ function logError( } function handleError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - logError(err, pass, fnLoc); + logError(err, context, fnLoc); if ( - pass.opts.panicThreshold === 'all_errors' || - (pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) || + context.opts.panicThreshold === 'all_errors' || + (context.opts.panicThreshold === 'critical_errors' && + isCriticalError(err)) || isConfigError(err) // Always throws regardless of panic threshold ) { throw err; @@ -187,7 +198,6 @@ export function createNewFunctionNode( } } // Avoid visiting the new transformed version - ALREADY_COMPILED.add(transformedFn); return transformedFn; } @@ -239,13 +249,6 @@ function insertNewOutlinedFunctionNode( } } -/* - * This is a hack to work around what seems to be a Babel bug. Babel doesn't - * consistently respect the `skip()` function to avoid revisiting a node within - * a pass, so we use this set to track nodes that we have compiled. - */ -const ALREADY_COMPILED: WeakSet | Set = new (WeakSet ?? Set)(); - const DEFAULT_ESLINT_SUPPRESSIONS = [ 'react-hooks/exhaustive-deps', 'react-hooks/rules-of-hooks', @@ -268,41 +271,43 @@ function isFilePartOfSources( return false; } -export type CompileProgramResult = { +export type CompileProgramMetadata = { retryErrors: Array<{fn: BabelFn; error: CompilerError}>; inferredEffectLocations: Set; }; /** - * `compileProgram` is directly invoked by the react-compiler babel plugin, so - * exceptions thrown by this function will fail the babel build. - * - call `handleError` if your error is recoverable. - * Unless the error is a warning / info diagnostic, compilation of a function - * / entire file should also be skipped. - * - throw an exception if the error is fatal / not recoverable. - * Examples of this are invalid compiler configs or failure to codegen outlined - * functions *after* already emitting optimized components / hooks that invoke - * the outlined functions. + * Main entrypoint for React Compiler. + * + * @param program The Babel program node to compile + * @param pass Compiler configuration and context + * @returns Compilation results or null if compilation was skipped */ export function compileProgram( program: NodePath, pass: CompilerPass, -): CompileProgramResult | null { +): CompileProgramMetadata | null { + /** + * This is directly invoked by the react-compiler babel plugin, so exceptions + * thrown by this function will fail the babel build. + * - call `handleError` if your error is recoverable. + * Unless the error is a warning / info diagnostic, compilation of a function + * / entire file should also be skipped. + * - throw an exception if the error is fatal / not recoverable. + * Examples of this are invalid compiler configs or failure to codegen outlined + * functions *after* already emitting optimized components / hooks that invoke + * the outlined functions. + */ if (shouldSkipCompilation(program, pass)) { return null; } - - const environment = pass.opts.environment; - const restrictedImportsErr = validateRestrictedImports(program, environment); + const restrictedImportsErr = validateRestrictedImports( + program, + pass.opts.environment, + ); if (restrictedImportsErr) { handleError(restrictedImportsErr, pass, null); return null; } - - const programContext = new ProgramContext( - program, - pass.opts.target, - environment.hookPattern, - ); /* * Record lint errors and critical errors as depending on Forget's config, * we may still need to run Forget's analysis on every function (even if we @@ -313,16 +318,88 @@ export function compileProgram( pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions, ); - const queue: Array<{ - kind: 'original' | 'outlined'; - fn: BabelFn; - fnType: ReactFunctionType; - }> = []; + + const programContext = new ProgramContext({ + program: program, + opts: pass.opts, + filename: pass.filename, + code: pass.code, + suppressions, + }); + + const queue: Array = findFunctionsToCompile( + program, + pass, + programContext, + ); const compiledFns: Array = []; + while (queue.length !== 0) { + const current = queue.shift()!; + const compiled = processFn(current.fn, current.fnType, programContext); + + if (compiled != null) { + for (const outlined of compiled.outlined) { + CompilerError.invariant(outlined.fn.outlined.length === 0, { + reason: 'Unexpected nested outlined functions', + loc: outlined.fn.loc, + }); + const fn = insertNewOutlinedFunctionNode( + program, + current.fn, + outlined.fn, + ); + fn.skip(); + programContext.alreadyCompiled.add(fn.node); + if (outlined.type !== null) { + queue.push({ + kind: 'outlined', + fn, + fnType: outlined.type, + }); + } + } + compiledFns.push({ + kind: current.kind, + originalFn: current.fn, + compiledFn: compiled, + }); + } + } + + // Avoid modifying the program if we find a program level opt-out + if (findDirectiveDisablingMemoization(program.node.directives) != null) { + return null; + } + + // Insert React Compiler generated functions into the Babel AST + applyCompiledFunctions(program, compiledFns, pass, programContext); + + return { + retryErrors: programContext.retryErrors, + inferredEffectLocations: programContext.inferredEffectLocations, + }; +} + +type CompileSource = { + kind: 'original' | 'outlined'; + fn: BabelFn; + fnType: ReactFunctionType; +}; +/** + * Find all React components and hooks that need to be compiled + * + * @returns An array of React functions from @param program to transform + */ +function findFunctionsToCompile( + program: NodePath, + pass: CompilerPass, + programContext: ProgramContext, +): Array { + const queue: Array = []; const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => { - const fnType = getReactFunctionType(fn, pass, environment); - if (fnType === null || ALREADY_COMPILED.has(fn.node)) { + const fnType = getReactFunctionType(fn, pass); + if (fnType === null || programContext.alreadyCompiled.has(fn.node)) { return; } @@ -331,7 +408,7 @@ export function compileProgram( * traversal will loop infinitely. * Ensure we avoid visiting the original function again. */ - ALREADY_COMPILED.add(fn.node); + programContext.alreadyCompiled.add(fn.node); fn.skip(); queue.push({kind: 'original', fn, fnType}); @@ -346,7 +423,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, ClassExpression(node: NodePath) { @@ -355,7 +431,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, FunctionDeclaration: traverseFunction, @@ -370,205 +445,205 @@ export function compileProgram( filename: pass.filename ?? null, }, ); - const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; - const inferredEffectLocations = new Set(); - const processFn = ( - fn: BabelFn, - fnType: ReactFunctionType, - ): null | CodegenFunction => { - let optInDirectives: Array = []; - let optOutDirectives: Array = []; - if (fn.node.body.type === 'BlockStatement') { - optInDirectives = findDirectiveEnablingMemoization( - fn.node.body.directives, - ); - optOutDirectives = findDirectiveDisablingMemoization( - fn.node.body.directives, - ); - } + return queue; +} - /** - * Note that Babel does not attach comment nodes to nodes; they are dangling off of the - * Program node itself. We need to figure out whether an eslint suppression range - * applies to this function first. - */ - const suppressionsInFunction = filterSuppressionsThatAffectFunction( - suppressions, - fn, - ); - let compileResult: - | {kind: 'compile'; compiledFn: CodegenFunction} - | {kind: 'error'; error: unknown}; - if (suppressionsInFunction.length > 0) { - compileResult = { - kind: 'error', - error: suppressionsToCompilerError(suppressionsInFunction), - }; +/** + * Try to compile a source function, taking into account all local suppressions, + * opt-ins, and opt-outs. + * + * Errors encountered during compilation are either logged (if recoverable) or + * thrown (if non-recoverable). + * + * @returns the compiled function or null if the function was skipped (due to + * config settings and/or outputs) + */ +function processFn( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): null | CodegenFunction { + let directives; + if (fn.node.body.type !== 'BlockStatement') { + directives = {optIn: null, optOut: null}; + } else { + directives = { + optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + }; + } + + let compiledFn: CodegenFunction; + const compileResult = tryCompileFunction(fn, fnType, programContext); + if (compileResult.kind === 'error') { + if (directives.optOut != null) { + logError(compileResult.error, programContext, fn.node.loc ?? null); } else { - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'all_features', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - } catch (err) { - compileResult = {kind: 'error', error: err}; - } + handleError(compileResult.error, programContext, fn.node.loc ?? null); } - - if (compileResult.kind === 'error') { - /** - * If an opt out directive is present, log only instead of throwing and don't mark as - * containing a critical error. - */ - if (optOutDirectives.length > 0) { - logError(compileResult.error, pass, fn.node.loc ?? null); - } else { - handleError(compileResult.error, pass, fn.node.loc ?? null); - } - // If non-memoization features are enabled, retry regardless of error kind - if ( - !(environment.enableFire || environment.inferEffectDependencies != null) - ) { - return null; - } - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'no_inferred_memo', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - if ( - !compileResult.compiledFn.hasFireRewrite && - !compileResult.compiledFn.hasInferredEffect - ) { - return null; - } - } catch (err) { - // TODO: we might want to log error here, but this will also result in duplicate logging - if (err instanceof CompilerError) { - retryErrors.push({fn, error: err}); - } - return null; - } - } - - /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. - */ - if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) { - for (const directive of optOutDirectives) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSkip', - fnLoc: fn.node.body.loc ?? null, - reason: `Skipped due to '${directive.value.value}' directive.`, - loc: directive.loc ?? null, - }); - } + const retryResult = retryCompileFunction(fn, fnType, programContext); + if (retryResult == null) { return null; } + compiledFn = retryResult; + } else { + compiledFn = compileResult.compiledFn; + } - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSuccess', - fnLoc: fn.node.loc ?? null, - fnName: compileResult.compiledFn.id?.name ?? null, - memoSlots: compileResult.compiledFn.memoSlotsUsed, - memoBlocks: compileResult.compiledFn.memoBlocks, - memoValues: compileResult.compiledFn.memoValues, - prunedMemoBlocks: compileResult.compiledFn.prunedMemoBlocks, - prunedMemoValues: compileResult.compiledFn.prunedMemoValues, + /** + * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler + * for validation but we don't mutate the babel AST. This allows us to flag if there is an + * unused 'use no forget/memo' directive. + */ + if ( + programContext.opts.ignoreUseNoForget === false && + directives.optOut != null + ) { + programContext.logEvent({ + kind: 'CompileSkip', + fnLoc: fn.node.body.loc ?? null, + reason: `Skipped due to '${directives.optOut.value}' directive.`, + loc: directives.optOut.loc ?? null, }); + return null; + } + programContext.logEvent({ + kind: 'CompileSuccess', + fnLoc: fn.node.loc ?? null, + fnName: compiledFn.id?.name ?? null, + memoSlots: compiledFn.memoSlotsUsed, + memoBlocks: compiledFn.memoBlocks, + memoValues: compiledFn.memoValues, + prunedMemoBlocks: compiledFn.prunedMemoBlocks, + prunedMemoValues: compiledFn.prunedMemoValues, + }); + /** + * Always compile functions with opt in directives. + */ + if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { /** - * Always compile functions with opt in directives. + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. */ - if (optInDirectives.length > 0) { - return compileResult.compiledFn; - } else if (pass.opts.compilationMode === 'annotation') { - /** - * No opt-in directive in annotation mode, so don't insert the compiled function. - */ - return null; - } - - if (!pass.opts.noEmit) { - return compileResult.compiledFn; - } + return null; + } else if (programContext.opts.noEmit) { /** * inferEffectDependencies + noEmit is currently only used for linting. In * this mode, add source locations for where the compiler *can* infer effect * dependencies. */ - for (const loc of compileResult.compiledFn.inferredEffectLocations) { - if (loc !== GeneratedSource) inferredEffectLocations.add(loc); - } - return null; - }; - - while (queue.length !== 0) { - const current = queue.shift()!; - const compiled = processFn(current.fn, current.fnType); - if (compiled === null) { - continue; - } - for (const outlined of compiled.outlined) { - CompilerError.invariant(outlined.fn.outlined.length === 0, { - reason: 'Unexpected nested outlined functions', - loc: outlined.fn.loc, - }); - const fn = insertNewOutlinedFunctionNode( - program, - current.fn, - outlined.fn, - ); - fn.skip(); - ALREADY_COMPILED.add(fn.node); - if (outlined.type !== null) { - queue.push({ - kind: 'outlined', - fn, - fnType: outlined.type, - }); + for (const loc of compiledFn.inferredEffectLocations) { + if (loc !== GeneratedSource) { + programContext.inferredEffectLocations.add(loc); } } - compiledFns.push({ - kind: current.kind, - compiledFn: compiled, - originalFn: current.fn, - }); + return null; + } else { + return compiledFn; + } +} + +function tryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): + | {kind: 'compile'; compiledFn: CodegenFunction} + | {kind: 'error'; error: unknown} { + /** + * Note that Babel does not attach comment nodes to nodes; they are dangling off of the + * Program node itself. We need to figure out whether an eslint suppression range + * applies to this function first. + */ + const suppressionsInFunction = filterSuppressionsThatAffectFunction( + programContext.suppressions, + fn, + ); + if (suppressionsInFunction.length > 0) { + return { + kind: 'error', + error: suppressionsToCompilerError(suppressionsInFunction), + }; } - /** - * Do not modify source if there is a module scope level opt out directive. - */ - const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization( - program.node.directives, - ); - if (moduleScopeOptOutDirectives.length > 0) { + try { + return { + kind: 'compile', + compiledFn: compileFn( + fn, + programContext.opts.environment, + fnType, + 'all_features', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ), + }; + } catch (err) { + return {kind: 'error', error: err}; + } +} + +/** + * If non-memo feature flags are enabled, retry compilation with a more minimal + * feature set. + * + * @returns a CodegenFunction if retry was successful + */ +function retryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): CodegenFunction | null { + const environment = programContext.opts.environment; + if ( + !(environment.enableFire || environment.inferEffectDependencies != null) + ) { return null; } - /* - * Only insert Forget-ified functions if we have not encountered a critical - * error elsewhere in the file, regardless of bailout mode. + /** + * Note that function suppressions are not checked in the retry pipeline, as + * they only affect auto-memoization features. */ + try { + const retryResult = compileFn( + fn, + environment, + fnType, + 'no_inferred_memo', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ); + + if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) { + return null; + } + return retryResult; + } catch (err) { + // TODO: we might want to log error here, but this will also result in duplicate logging + if (err instanceof CompilerError) { + programContext.retryErrors.push({fn, error: err}); + } + return null; + } +} + +/** + * Applies React Compiler generated functions to the babel AST by replacing + * existing functions in place or inserting new declarations. + */ +function applyCompiledFunctions( + program: NodePath, + compiledFns: Array, + pass: CompilerPass, + programContext: ProgramContext, +): void { const referencedBeforeDeclared = pass.opts.gating != null ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) @@ -576,6 +651,7 @@ export function compileProgram( for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); + programContext.alreadyCompiled.add(transformedFn); if (referencedBeforeDeclared != null && kind === 'original') { CompilerError.invariant(pass.opts.gating != null, { @@ -598,7 +674,6 @@ export function compileProgram( if (compiledFns.length > 0) { addImportsToProgram(program, programContext); } - return {retryErrors, inferredEffectLocations}; } function shouldSkipCompilation( @@ -640,14 +715,10 @@ function shouldSkipCompilation( function getReactFunctionType( fn: BabelFn, pass: CompilerPass, - /** - * TODO(mofeiZ): remove once we validate PluginOptions with Zod - */ - environment: EnvironmentConfig, ): ReactFunctionType | null { - const hookPattern = environment.hookPattern; + const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0) + if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 5f6c6986e0..e288c227ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -18,7 +18,7 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {Environment} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; -import {CompileProgramResult} from './Program'; +import {CompileProgramMetadata} from './Program'; function throwInvalidReact( options: Omit, @@ -109,7 +109,7 @@ export default function validateNoUntransformedReferences( filename: string | null, logger: Logger | null, env: EnvironmentConfig, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const moduleLoadChecks = new Map< string, @@ -236,7 +236,7 @@ function transformProgram( moduleLoadChecks: Map>, filename: string | null, logger: Logger | null, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const traversalState: TraversalState = { shouldInvalidateScopes: true, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md new file mode 100644 index 0000000000..bd70c0138d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo({ propVal }) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js new file mode 100644 index 0000000000..d1dda06a04 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js @@ -0,0 +1,19 @@ +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md new file mode 100644 index 0000000000..8d2e5c7c31 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md @@ -0,0 +1,60 @@ + +## Input + +```javascript +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + 'use memo'; + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo(t0) { + "use memo"; + const { propVal } = t0; + + const arr = [propVal]; + useEffectWrapper(() => print(arr), [arr]); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal), [arr2, propVal]); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js new file mode 100644 index 0000000000..2650cbe5d7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js @@ -0,0 +1,21 @@ +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + 'use memo'; + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; From e247982f689bd362dd08d10a10cc79d1441f48fb Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 15:44:30 -0400 Subject: [PATCH 389/422] [compiler][entrypoint] Fix edgecases for noEmit and opt-outs Title --- .../src/Entrypoint/Imports.ts | 4 ++ .../src/Entrypoint/Program.ts | 43 +++++++++++++------ .../no-emit/retry-opt-in--no-emit.expect.md | 9 ++-- ...bailout-nopanic-shouldnt-outline.expect.md | 3 -- .../compiler/use-memo-noemit.expect.md | 15 +------ 5 files changed, 39 insertions(+), 35 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index d5cac921e9..24ce37cf72 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -59,6 +59,7 @@ type ProgramContextOptions = { opts: PluginOptions; filename: string | null; code: string | null; + hasModuleScopeOptOut: boolean; }; export class ProgramContext { /** @@ -70,6 +71,7 @@ export class ProgramContext { code: string | null; reactRuntimeModule: string; suppressions: Array; + hasModuleScopeOptOut: boolean; /* * This is a hack to work around what seems to be a Babel bug. Babel doesn't @@ -94,6 +96,7 @@ export class ProgramContext { opts, filename, code, + hasModuleScopeOptOut, }: ProgramContextOptions) { this.scope = program.scope; this.opts = opts; @@ -101,6 +104,7 @@ export class ProgramContext { this.code = code; this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); this.suppressions = suppressions; + this.hasModuleScopeOptOut = hasModuleScopeOptOut; } isHookName(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 1a3445531d..64abc110ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -325,6 +325,8 @@ export function compileProgram( filename: pass.filename, code: pass.code, suppressions, + hasModuleScopeOptOut: + findDirectiveDisablingMemoization(program.node.directives) != null, }); const queue: Array = findFunctionsToCompile( @@ -368,7 +370,19 @@ export function compileProgram( } // Avoid modifying the program if we find a program level opt-out - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } return null; } @@ -491,9 +505,10 @@ function processFn( } /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. + * If 'use no forget/memo' is present and we still ran the code through the + * compiler for validation, log a skip event and don't mutate the babel AST. + * This allows us to flag if there is an unused 'use no forget/memo' + * directive. */ if ( programContext.opts.ignoreUseNoForget === false && @@ -518,16 +533,7 @@ function processFn( prunedMemoValues: compiledFn.prunedMemoValues, }); - /** - * Always compile functions with opt in directives. - */ - if (directives.optIn != null) { - return compiledFn; - } else if (programContext.opts.compilationMode === 'annotation') { - /** - * If no opt-in directive is found and the compiler is configured in - * annotation mode, don't insert the compiled function. - */ + if (programContext.hasModuleScopeOptOut) { return null; } else if (programContext.opts.noEmit) { /** @@ -541,6 +547,15 @@ function processFn( } } return null; + } else if ( + programContext.opts.compilationMode === 'annotation' && + directives.optIn == null + ) { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; } else { return compiledFn; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md index 8d2e5c7c31..6e4ddeeb2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md @@ -33,16 +33,15 @@ export const FIXTURE_ENTRYPOINT = { import { print } from "shared-runtime"; import useEffectWrapper from "useEffectWrapper"; -function Foo(t0) { +function Foo({ propVal }) { "use memo"; - const { propVal } = t0; - const arr = [propVal]; - useEffectWrapper(() => print(arr), [arr]); + useEffectWrapper(() => print(arr)); const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), [arr2, propVal]); + useEffectWrapper(() => arr2.push(propVal)); arr2.push(2); + return { arr, arr2 }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md index f24d949205..cfbaa34568 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -20,9 +20,6 @@ function Foo() { function Foo() { return ; } -function _temp() { - return alert("hello!"); -} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md index dfc831555f..c47501945b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -19,22 +19,11 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @noEmit +// @noEmit function Foo() { "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return alert("hello!"); + return ; } export const FIXTURE_ENTRYPOINT = { From 5b7cdf702e16f14c90724bcfc826dfa3640a5e49 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 16:39:22 -0400 Subject: [PATCH 390/422] [compiler][gating] Experimental directive based gating Adds `dynamicGating` as an experimental option for testing rollout DX at Meta. If specified, this enables dynamic gating which matches `use memo if(...)` directives. #### Example usage Input file ```js // @dynamicGating:{"source":"myModule"} export function MyComponent() { 'use memo if(isEnabled)'; return
...
; } ``` Compiler output ```js import {isEnabled} from 'myModule'; export const MyComponent = isEnabled() ? : ; ``` --- .../src/Entrypoint/Options.ts | 46 ++++++ .../src/Entrypoint/Program.ts | 143 +++++++++++++++--- .../dynamic-gating-annotation.expect.md | 50 ++++++ .../gating/dynamic-gating-annotation.js | 11 ++ .../dynamic-gating-bailout-nopanic.expect.md | 66 ++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 ++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 ++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ .../dynamic-gating-invalid-multiple.expect.md | 45 ++++++ .../gating/dynamic-gating-invalid-multiple.js | 12 ++ .../gating/dynamic-gating-noemit.expect.md | 37 +++++ .../compiler/gating/dynamic-gating-noemit.js | 11 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 ++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../error.todo-dynamic-gating.expect.md | 42 +++++ .../error.todo-dynamic-gating.js | 21 +++ .../bailout-retry/error.todo-gating.expect.md | 40 +++++ .../bailout-retry/error.todo-gating.js | 19 +++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 + 26 files changed, 814 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..96cce887d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,28 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + * ``` + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +270,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +319,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 64abc110ea..cb57bd2c49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,64 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else if (result.length > 1) { + const error = new CompilerError(); + error.push({ + reason: `Multiple dynamic gating directives found`, + description: `Expected a single directive but found [${result + .map(r => r.directive.value.value) + .join(', ')}]`, + severity: ErrorSeverity.InvalidReact, + loc: result[0].directive.loc ?? null, + suggestions: null, + }); + return Err(error); + } else if (result.length === 1) { + return Ok({ + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + }); + } else { + return Ok(null); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -477,12 +545,32 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we handle + * the error and fall back to the safest option which is to not optimize + * the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -659,25 +747,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -733,8 +827,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.unwrapOr(null) != null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md new file mode 100644 index 0000000000..364239e4e3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js new file mode 100644 index 0000000000..c30b30fe6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md new file mode 100644 index 0000000000..327adbe792 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + "use memo if(getTrue)"; + "use memo if(getFalse)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}} +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js new file mode 100644 index 0000000000..867ac8ee34 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js @@ -0,0 +1,12 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md new file mode 100644 index 0000000000..81ebd6dd9f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + "use memo if(getTrue)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js new file mode 100644 index 0000000000..97cf777a55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md new file mode 100644 index 0000000000..ec5ef238b7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md @@ -0,0 +1,42 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none" + +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + 'use memo if(getTrue)'; + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; + +``` + + +## Error + +``` + 10 | 'use memo if(getTrue)'; + 11 | const arr = []; +> 12 | useEffectWrapper(() => arr.push(foo)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (12:12) + 13 | arr.push(2); + 14 | return arr; + 15 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js new file mode 100644 index 0000000000..4d1ceb92b7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js @@ -0,0 +1,21 @@ +// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none" + +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + 'use memo if(getTrue)'; + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md new file mode 100644 index 0000000000..e071e37cb9 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// @gating @inferEffectDependencies @panicThreshold:"none" +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; + +``` + + +## Error + +``` + 8 | function Component({foo}) { + 9 | const arr = []; +> 10 | useEffectWrapper(() => arr.push(foo)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (10:10) + 11 | arr.push(2); + 12 | return arr; + 13 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js new file mode 100644 index 0000000000..651b24074f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js @@ -0,0 +1,19 @@ +// @gating @inferEffectDependencies @panicThreshold:"none" +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From d80c6a2add68d7c2265aac5804451ddc34529580 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 19:06:48 -0400 Subject: [PATCH 391/422] [compiler][be] Make program traversal more readable React Compiler's program traversal logic is pretty lengthy and complex as we've added a lot of features piecemeal. `compileProgram` is 300+ lines long and has confusing control flow (defining helpers inline, invoking visitors, mutating-asts-while-iterating, mutating global `ALREADY_COMPILED` state). - Moved more stuff to `ProgramContext` - Separated `compileProgram` into a bunch of helpers Will come back and add more comments inline --- .../src/Entrypoint/Imports.ts | 62 +- .../src/Entrypoint/Program.ts | 545 ++++++++++-------- .../ValidateNoUntransformedReferences.ts | 6 +- .../no-emit-lint-repro.expect.md | 0 .../{ => no-emit}/no-emit-lint-repro.js | 0 .../no-emit/retry-no-emit.expect.md | 64 ++ .../no-emit/retry-no-emit.js | 19 + .../no-emit/retry-opt-in--no-emit.expect.md | 60 ++ .../no-emit/retry-opt-in--no-emit.js | 21 + 9 files changed, 524 insertions(+), 253 deletions(-) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/{ => no-emit}/no-emit-lint-repro.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/{ => no-emit}/no-emit-lint-repro.js (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 704f696b3b..d5cac921e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -18,8 +18,9 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; -import {CompilerReactTarget} from './Options'; -import {getReactCompilerRuntimeModule} from './Program'; +import {LoggerEvent, PluginOptions} from './Options'; +import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( path: NodePath, @@ -52,32 +53,61 @@ export function validateRestrictedImports( } } +type ProgramContextOptions = { + program: NodePath; + suppressions: Array; + opts: PluginOptions; + filename: string | null; + code: string | null; +}; export class ProgramContext { - /* Program and environment context */ + /** + * Program and environment context + */ scope: BabelScope; + opts: PluginOptions; + filename: string | null; + code: string | null; reactRuntimeModule: string; - hookPattern: string | null; + suppressions: Array; + /* + * This is a hack to work around what seems to be a Babel bug. Babel doesn't + * consistently respect the `skip()` function to avoid revisiting a node within + * a pass, so we use this set to track nodes that we have compiled. + */ + alreadyCompiled: WeakSet | Set = new (WeakSet ?? Set)(); // known generated or referenced identifiers in the program knownReferencedNames: Set = new Set(); // generated imports imports: Map> = new Map(); - constructor( - program: NodePath, - reactRuntimeModule: CompilerReactTarget, - hookPattern: string | null, - ) { - this.hookPattern = hookPattern; + /** + * Metadata from compilation + */ + retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; + inferredEffectLocations: Set = new Set(); + + constructor({ + program, + suppressions, + opts, + filename, + code, + }: ProgramContextOptions) { this.scope = program.scope; - this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule); + this.opts = opts; + this.filename = filename; + this.code = code; + this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); + this.suppressions = suppressions; } isHookName(name: string): boolean { - if (this.hookPattern == null) { + if (this.opts.environment.hookPattern == null) { return isHookName(name); } else { - const match = new RegExp(this.hookPattern).exec(name); + const match = new RegExp(this.opts.environment.hookPattern).exec(name); return ( match != null && typeof match[1] === 'string' && isHookName(match[1]) ); @@ -179,6 +209,12 @@ export class ProgramContext { }); return Err(error); } + + logEvent(event: LoggerEvent): void { + if (this.opts.logger != null) { + this.opts.logger.logEvent(this.filename, event); + } + } } function getExistingImports( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 34347f10b8..1a3445531d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment'; +import {ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -43,17 +43,21 @@ export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); export function findDirectiveEnablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), + ) ?? null ); } export function findDirectiveDisablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_OUT_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_OUT_DIRECTIVES.has(directive.value.value), + ) ?? null ); } @@ -88,13 +92,16 @@ export type CompileResult = { function logError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - if (pass.opts.logger) { + if (context.opts.logger) { if (err instanceof CompilerError) { for (const detail of err.details) { - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, detail: detail.options, @@ -108,7 +115,7 @@ function logError( stringifiedError = err?.toString() ?? '[ null ]'; } - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'PipelineError', fnLoc, data: stringifiedError, @@ -118,13 +125,17 @@ function logError( } function handleError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - logError(err, pass, fnLoc); + logError(err, context, fnLoc); if ( - pass.opts.panicThreshold === 'all_errors' || - (pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) || + context.opts.panicThreshold === 'all_errors' || + (context.opts.panicThreshold === 'critical_errors' && + isCriticalError(err)) || isConfigError(err) // Always throws regardless of panic threshold ) { throw err; @@ -187,7 +198,6 @@ export function createNewFunctionNode( } } // Avoid visiting the new transformed version - ALREADY_COMPILED.add(transformedFn); return transformedFn; } @@ -239,13 +249,6 @@ function insertNewOutlinedFunctionNode( } } -/* - * This is a hack to work around what seems to be a Babel bug. Babel doesn't - * consistently respect the `skip()` function to avoid revisiting a node within - * a pass, so we use this set to track nodes that we have compiled. - */ -const ALREADY_COMPILED: WeakSet | Set = new (WeakSet ?? Set)(); - const DEFAULT_ESLINT_SUPPRESSIONS = [ 'react-hooks/exhaustive-deps', 'react-hooks/rules-of-hooks', @@ -268,41 +271,43 @@ function isFilePartOfSources( return false; } -export type CompileProgramResult = { +export type CompileProgramMetadata = { retryErrors: Array<{fn: BabelFn; error: CompilerError}>; inferredEffectLocations: Set; }; /** - * `compileProgram` is directly invoked by the react-compiler babel plugin, so - * exceptions thrown by this function will fail the babel build. - * - call `handleError` if your error is recoverable. - * Unless the error is a warning / info diagnostic, compilation of a function - * / entire file should also be skipped. - * - throw an exception if the error is fatal / not recoverable. - * Examples of this are invalid compiler configs or failure to codegen outlined - * functions *after* already emitting optimized components / hooks that invoke - * the outlined functions. + * Main entrypoint for React Compiler. + * + * @param program The Babel program node to compile + * @param pass Compiler configuration and context + * @returns Compilation results or null if compilation was skipped */ export function compileProgram( program: NodePath, pass: CompilerPass, -): CompileProgramResult | null { +): CompileProgramMetadata | null { + /** + * This is directly invoked by the react-compiler babel plugin, so exceptions + * thrown by this function will fail the babel build. + * - call `handleError` if your error is recoverable. + * Unless the error is a warning / info diagnostic, compilation of a function + * / entire file should also be skipped. + * - throw an exception if the error is fatal / not recoverable. + * Examples of this are invalid compiler configs or failure to codegen outlined + * functions *after* already emitting optimized components / hooks that invoke + * the outlined functions. + */ if (shouldSkipCompilation(program, pass)) { return null; } - - const environment = pass.opts.environment; - const restrictedImportsErr = validateRestrictedImports(program, environment); + const restrictedImportsErr = validateRestrictedImports( + program, + pass.opts.environment, + ); if (restrictedImportsErr) { handleError(restrictedImportsErr, pass, null); return null; } - - const programContext = new ProgramContext( - program, - pass.opts.target, - environment.hookPattern, - ); /* * Record lint errors and critical errors as depending on Forget's config, * we may still need to run Forget's analysis on every function (even if we @@ -313,16 +318,88 @@ export function compileProgram( pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions, ); - const queue: Array<{ - kind: 'original' | 'outlined'; - fn: BabelFn; - fnType: ReactFunctionType; - }> = []; + + const programContext = new ProgramContext({ + program: program, + opts: pass.opts, + filename: pass.filename, + code: pass.code, + suppressions, + }); + + const queue: Array = findFunctionsToCompile( + program, + pass, + programContext, + ); const compiledFns: Array = []; + while (queue.length !== 0) { + const current = queue.shift()!; + const compiled = processFn(current.fn, current.fnType, programContext); + + if (compiled != null) { + for (const outlined of compiled.outlined) { + CompilerError.invariant(outlined.fn.outlined.length === 0, { + reason: 'Unexpected nested outlined functions', + loc: outlined.fn.loc, + }); + const fn = insertNewOutlinedFunctionNode( + program, + current.fn, + outlined.fn, + ); + fn.skip(); + programContext.alreadyCompiled.add(fn.node); + if (outlined.type !== null) { + queue.push({ + kind: 'outlined', + fn, + fnType: outlined.type, + }); + } + } + compiledFns.push({ + kind: current.kind, + originalFn: current.fn, + compiledFn: compiled, + }); + } + } + + // Avoid modifying the program if we find a program level opt-out + if (findDirectiveDisablingMemoization(program.node.directives) != null) { + return null; + } + + // Insert React Compiler generated functions into the Babel AST + applyCompiledFunctions(program, compiledFns, pass, programContext); + + return { + retryErrors: programContext.retryErrors, + inferredEffectLocations: programContext.inferredEffectLocations, + }; +} + +type CompileSource = { + kind: 'original' | 'outlined'; + fn: BabelFn; + fnType: ReactFunctionType; +}; +/** + * Find all React components and hooks that need to be compiled + * + * @returns An array of React functions from @param program to transform + */ +function findFunctionsToCompile( + program: NodePath, + pass: CompilerPass, + programContext: ProgramContext, +): Array { + const queue: Array = []; const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => { - const fnType = getReactFunctionType(fn, pass, environment); - if (fnType === null || ALREADY_COMPILED.has(fn.node)) { + const fnType = getReactFunctionType(fn, pass); + if (fnType === null || programContext.alreadyCompiled.has(fn.node)) { return; } @@ -331,7 +408,7 @@ export function compileProgram( * traversal will loop infinitely. * Ensure we avoid visiting the original function again. */ - ALREADY_COMPILED.add(fn.node); + programContext.alreadyCompiled.add(fn.node); fn.skip(); queue.push({kind: 'original', fn, fnType}); @@ -346,7 +423,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, ClassExpression(node: NodePath) { @@ -355,7 +431,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, FunctionDeclaration: traverseFunction, @@ -370,205 +445,205 @@ export function compileProgram( filename: pass.filename ?? null, }, ); - const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; - const inferredEffectLocations = new Set(); - const processFn = ( - fn: BabelFn, - fnType: ReactFunctionType, - ): null | CodegenFunction => { - let optInDirectives: Array = []; - let optOutDirectives: Array = []; - if (fn.node.body.type === 'BlockStatement') { - optInDirectives = findDirectiveEnablingMemoization( - fn.node.body.directives, - ); - optOutDirectives = findDirectiveDisablingMemoization( - fn.node.body.directives, - ); - } + return queue; +} - /** - * Note that Babel does not attach comment nodes to nodes; they are dangling off of the - * Program node itself. We need to figure out whether an eslint suppression range - * applies to this function first. - */ - const suppressionsInFunction = filterSuppressionsThatAffectFunction( - suppressions, - fn, - ); - let compileResult: - | {kind: 'compile'; compiledFn: CodegenFunction} - | {kind: 'error'; error: unknown}; - if (suppressionsInFunction.length > 0) { - compileResult = { - kind: 'error', - error: suppressionsToCompilerError(suppressionsInFunction), - }; +/** + * Try to compile a source function, taking into account all local suppressions, + * opt-ins, and opt-outs. + * + * Errors encountered during compilation are either logged (if recoverable) or + * thrown (if non-recoverable). + * + * @returns the compiled function or null if the function was skipped (due to + * config settings and/or outputs) + */ +function processFn( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): null | CodegenFunction { + let directives; + if (fn.node.body.type !== 'BlockStatement') { + directives = {optIn: null, optOut: null}; + } else { + directives = { + optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + }; + } + + let compiledFn: CodegenFunction; + const compileResult = tryCompileFunction(fn, fnType, programContext); + if (compileResult.kind === 'error') { + if (directives.optOut != null) { + logError(compileResult.error, programContext, fn.node.loc ?? null); } else { - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'all_features', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - } catch (err) { - compileResult = {kind: 'error', error: err}; - } + handleError(compileResult.error, programContext, fn.node.loc ?? null); } - - if (compileResult.kind === 'error') { - /** - * If an opt out directive is present, log only instead of throwing and don't mark as - * containing a critical error. - */ - if (optOutDirectives.length > 0) { - logError(compileResult.error, pass, fn.node.loc ?? null); - } else { - handleError(compileResult.error, pass, fn.node.loc ?? null); - } - // If non-memoization features are enabled, retry regardless of error kind - if ( - !(environment.enableFire || environment.inferEffectDependencies != null) - ) { - return null; - } - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'no_inferred_memo', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - if ( - !compileResult.compiledFn.hasFireRewrite && - !compileResult.compiledFn.hasInferredEffect - ) { - return null; - } - } catch (err) { - // TODO: we might want to log error here, but this will also result in duplicate logging - if (err instanceof CompilerError) { - retryErrors.push({fn, error: err}); - } - return null; - } - } - - /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. - */ - if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) { - for (const directive of optOutDirectives) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSkip', - fnLoc: fn.node.body.loc ?? null, - reason: `Skipped due to '${directive.value.value}' directive.`, - loc: directive.loc ?? null, - }); - } + const retryResult = retryCompileFunction(fn, fnType, programContext); + if (retryResult == null) { return null; } + compiledFn = retryResult; + } else { + compiledFn = compileResult.compiledFn; + } - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSuccess', - fnLoc: fn.node.loc ?? null, - fnName: compileResult.compiledFn.id?.name ?? null, - memoSlots: compileResult.compiledFn.memoSlotsUsed, - memoBlocks: compileResult.compiledFn.memoBlocks, - memoValues: compileResult.compiledFn.memoValues, - prunedMemoBlocks: compileResult.compiledFn.prunedMemoBlocks, - prunedMemoValues: compileResult.compiledFn.prunedMemoValues, + /** + * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler + * for validation but we don't mutate the babel AST. This allows us to flag if there is an + * unused 'use no forget/memo' directive. + */ + if ( + programContext.opts.ignoreUseNoForget === false && + directives.optOut != null + ) { + programContext.logEvent({ + kind: 'CompileSkip', + fnLoc: fn.node.body.loc ?? null, + reason: `Skipped due to '${directives.optOut.value}' directive.`, + loc: directives.optOut.loc ?? null, }); + return null; + } + programContext.logEvent({ + kind: 'CompileSuccess', + fnLoc: fn.node.loc ?? null, + fnName: compiledFn.id?.name ?? null, + memoSlots: compiledFn.memoSlotsUsed, + memoBlocks: compiledFn.memoBlocks, + memoValues: compiledFn.memoValues, + prunedMemoBlocks: compiledFn.prunedMemoBlocks, + prunedMemoValues: compiledFn.prunedMemoValues, + }); + /** + * Always compile functions with opt in directives. + */ + if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { /** - * Always compile functions with opt in directives. + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. */ - if (optInDirectives.length > 0) { - return compileResult.compiledFn; - } else if (pass.opts.compilationMode === 'annotation') { - /** - * No opt-in directive in annotation mode, so don't insert the compiled function. - */ - return null; - } - - if (!pass.opts.noEmit) { - return compileResult.compiledFn; - } + return null; + } else if (programContext.opts.noEmit) { /** * inferEffectDependencies + noEmit is currently only used for linting. In * this mode, add source locations for where the compiler *can* infer effect * dependencies. */ - for (const loc of compileResult.compiledFn.inferredEffectLocations) { - if (loc !== GeneratedSource) inferredEffectLocations.add(loc); - } - return null; - }; - - while (queue.length !== 0) { - const current = queue.shift()!; - const compiled = processFn(current.fn, current.fnType); - if (compiled === null) { - continue; - } - for (const outlined of compiled.outlined) { - CompilerError.invariant(outlined.fn.outlined.length === 0, { - reason: 'Unexpected nested outlined functions', - loc: outlined.fn.loc, - }); - const fn = insertNewOutlinedFunctionNode( - program, - current.fn, - outlined.fn, - ); - fn.skip(); - ALREADY_COMPILED.add(fn.node); - if (outlined.type !== null) { - queue.push({ - kind: 'outlined', - fn, - fnType: outlined.type, - }); + for (const loc of compiledFn.inferredEffectLocations) { + if (loc !== GeneratedSource) { + programContext.inferredEffectLocations.add(loc); } } - compiledFns.push({ - kind: current.kind, - compiledFn: compiled, - originalFn: current.fn, - }); + return null; + } else { + return compiledFn; + } +} + +function tryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): + | {kind: 'compile'; compiledFn: CodegenFunction} + | {kind: 'error'; error: unknown} { + /** + * Note that Babel does not attach comment nodes to nodes; they are dangling off of the + * Program node itself. We need to figure out whether an eslint suppression range + * applies to this function first. + */ + const suppressionsInFunction = filterSuppressionsThatAffectFunction( + programContext.suppressions, + fn, + ); + if (suppressionsInFunction.length > 0) { + return { + kind: 'error', + error: suppressionsToCompilerError(suppressionsInFunction), + }; } - /** - * Do not modify source if there is a module scope level opt out directive. - */ - const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization( - program.node.directives, - ); - if (moduleScopeOptOutDirectives.length > 0) { + try { + return { + kind: 'compile', + compiledFn: compileFn( + fn, + programContext.opts.environment, + fnType, + 'all_features', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ), + }; + } catch (err) { + return {kind: 'error', error: err}; + } +} + +/** + * If non-memo feature flags are enabled, retry compilation with a more minimal + * feature set. + * + * @returns a CodegenFunction if retry was successful + */ +function retryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): CodegenFunction | null { + const environment = programContext.opts.environment; + if ( + !(environment.enableFire || environment.inferEffectDependencies != null) + ) { return null; } - /* - * Only insert Forget-ified functions if we have not encountered a critical - * error elsewhere in the file, regardless of bailout mode. + /** + * Note that function suppressions are not checked in the retry pipeline, as + * they only affect auto-memoization features. */ + try { + const retryResult = compileFn( + fn, + environment, + fnType, + 'no_inferred_memo', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ); + + if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) { + return null; + } + return retryResult; + } catch (err) { + // TODO: we might want to log error here, but this will also result in duplicate logging + if (err instanceof CompilerError) { + programContext.retryErrors.push({fn, error: err}); + } + return null; + } +} + +/** + * Applies React Compiler generated functions to the babel AST by replacing + * existing functions in place or inserting new declarations. + */ +function applyCompiledFunctions( + program: NodePath, + compiledFns: Array, + pass: CompilerPass, + programContext: ProgramContext, +): void { const referencedBeforeDeclared = pass.opts.gating != null ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) @@ -576,6 +651,7 @@ export function compileProgram( for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); + programContext.alreadyCompiled.add(transformedFn); if (referencedBeforeDeclared != null && kind === 'original') { CompilerError.invariant(pass.opts.gating != null, { @@ -598,7 +674,6 @@ export function compileProgram( if (compiledFns.length > 0) { addImportsToProgram(program, programContext); } - return {retryErrors, inferredEffectLocations}; } function shouldSkipCompilation( @@ -640,14 +715,10 @@ function shouldSkipCompilation( function getReactFunctionType( fn: BabelFn, pass: CompilerPass, - /** - * TODO(mofeiZ): remove once we validate PluginOptions with Zod - */ - environment: EnvironmentConfig, ): ReactFunctionType | null { - const hookPattern = environment.hookPattern; + const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0) + if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 5f6c6986e0..e288c227ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -18,7 +18,7 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {Environment} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; -import {CompileProgramResult} from './Program'; +import {CompileProgramMetadata} from './Program'; function throwInvalidReact( options: Omit, @@ -109,7 +109,7 @@ export default function validateNoUntransformedReferences( filename: string | null, logger: Logger | null, env: EnvironmentConfig, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const moduleLoadChecks = new Map< string, @@ -236,7 +236,7 @@ function transformProgram( moduleLoadChecks: Map>, filename: string | null, logger: Logger | null, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const traversalState: TraversalState = { shouldInvalidateScopes: true, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md new file mode 100644 index 0000000000..bd70c0138d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo({ propVal }) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js new file mode 100644 index 0000000000..d1dda06a04 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js @@ -0,0 +1,19 @@ +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md new file mode 100644 index 0000000000..8d2e5c7c31 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md @@ -0,0 +1,60 @@ + +## Input + +```javascript +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + 'use memo'; + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo(t0) { + "use memo"; + const { propVal } = t0; + + const arr = [propVal]; + useEffectWrapper(() => print(arr), [arr]); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal), [arr2, propVal]); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js new file mode 100644 index 0000000000..2650cbe5d7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js @@ -0,0 +1,21 @@ +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + 'use memo'; + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; From 6d07fd0386c24aa624440ad190c16ce2c4bf28ad Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 19:06:48 -0400 Subject: [PATCH 392/422] [compiler][entrypoint] Fix edgecases for noEmit and opt-outs Title --- .../src/Entrypoint/Imports.ts | 4 ++ .../src/Entrypoint/Program.ts | 43 +++++++++++++------ .../no-emit/retry-opt-in--no-emit.expect.md | 9 ++-- ...bailout-nopanic-shouldnt-outline.expect.md | 3 -- .../compiler/use-memo-noemit.expect.md | 15 +------ 5 files changed, 39 insertions(+), 35 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index d5cac921e9..24ce37cf72 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -59,6 +59,7 @@ type ProgramContextOptions = { opts: PluginOptions; filename: string | null; code: string | null; + hasModuleScopeOptOut: boolean; }; export class ProgramContext { /** @@ -70,6 +71,7 @@ export class ProgramContext { code: string | null; reactRuntimeModule: string; suppressions: Array; + hasModuleScopeOptOut: boolean; /* * This is a hack to work around what seems to be a Babel bug. Babel doesn't @@ -94,6 +96,7 @@ export class ProgramContext { opts, filename, code, + hasModuleScopeOptOut, }: ProgramContextOptions) { this.scope = program.scope; this.opts = opts; @@ -101,6 +104,7 @@ export class ProgramContext { this.code = code; this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); this.suppressions = suppressions; + this.hasModuleScopeOptOut = hasModuleScopeOptOut; } isHookName(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 1a3445531d..64abc110ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -325,6 +325,8 @@ export function compileProgram( filename: pass.filename, code: pass.code, suppressions, + hasModuleScopeOptOut: + findDirectiveDisablingMemoization(program.node.directives) != null, }); const queue: Array = findFunctionsToCompile( @@ -368,7 +370,19 @@ export function compileProgram( } // Avoid modifying the program if we find a program level opt-out - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } return null; } @@ -491,9 +505,10 @@ function processFn( } /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. + * If 'use no forget/memo' is present and we still ran the code through the + * compiler for validation, log a skip event and don't mutate the babel AST. + * This allows us to flag if there is an unused 'use no forget/memo' + * directive. */ if ( programContext.opts.ignoreUseNoForget === false && @@ -518,16 +533,7 @@ function processFn( prunedMemoValues: compiledFn.prunedMemoValues, }); - /** - * Always compile functions with opt in directives. - */ - if (directives.optIn != null) { - return compiledFn; - } else if (programContext.opts.compilationMode === 'annotation') { - /** - * If no opt-in directive is found and the compiler is configured in - * annotation mode, don't insert the compiled function. - */ + if (programContext.hasModuleScopeOptOut) { return null; } else if (programContext.opts.noEmit) { /** @@ -541,6 +547,15 @@ function processFn( } } return null; + } else if ( + programContext.opts.compilationMode === 'annotation' && + directives.optIn == null + ) { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; } else { return compiledFn; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md index 8d2e5c7c31..6e4ddeeb2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md @@ -33,16 +33,15 @@ export const FIXTURE_ENTRYPOINT = { import { print } from "shared-runtime"; import useEffectWrapper from "useEffectWrapper"; -function Foo(t0) { +function Foo({ propVal }) { "use memo"; - const { propVal } = t0; - const arr = [propVal]; - useEffectWrapper(() => print(arr), [arr]); + useEffectWrapper(() => print(arr)); const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), [arr2, propVal]); + useEffectWrapper(() => arr2.push(propVal)); arr2.push(2); + return { arr, arr2 }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md index f24d949205..cfbaa34568 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -20,9 +20,6 @@ function Foo() { function Foo() { return ; } -function _temp() { - return alert("hello!"); -} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md index dfc831555f..c47501945b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -19,22 +19,11 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @noEmit +// @noEmit function Foo() { "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return alert("hello!"); + return ; } export const FIXTURE_ENTRYPOINT = { From a629286f4d07539c319b7cae741968b11c9f1dc7 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 May 2025 19:06:48 -0400 Subject: [PATCH 393/422] [compiler][gating] Experimental directive based gating Adds `dynamicGating` as an experimental option for testing rollout DX at Meta. If specified, this enables dynamic gating which matches `use memo if(...)` directives. #### Example usage Input file ```js // @dynamicGating:{"source":"myModule"} export function MyComponent() { 'use memo if(isEnabled)'; return
...
; } ``` Compiler output ```js import {isEnabled} from 'myModule'; export const MyComponent = isEnabled() ? : ; ``` --- .../src/Entrypoint/Options.ts | 46 ++++++ .../src/Entrypoint/Program.ts | 143 +++++++++++++++--- .../dynamic-gating-annotation.expect.md | 50 ++++++ .../gating/dynamic-gating-annotation.js | 11 ++ .../dynamic-gating-bailout-nopanic.expect.md | 66 ++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 ++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 ++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ .../dynamic-gating-invalid-multiple.expect.md | 45 ++++++ .../gating/dynamic-gating-invalid-multiple.js | 12 ++ .../gating/dynamic-gating-noemit.expect.md | 37 +++++ .../compiler/gating/dynamic-gating-noemit.js | 11 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 ++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../error.todo-dynamic-gating.expect.md | 42 +++++ .../error.todo-dynamic-gating.js | 21 +++ .../bailout-retry/error.todo-gating.expect.md | 40 +++++ .../bailout-retry/error.todo-gating.js | 19 +++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 + 26 files changed, 814 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..96cce887d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,28 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + * ``` + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +270,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +319,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 64abc110ea..cb57bd2c49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,64 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else if (result.length > 1) { + const error = new CompilerError(); + error.push({ + reason: `Multiple dynamic gating directives found`, + description: `Expected a single directive but found [${result + .map(r => r.directive.value.value) + .join(', ')}]`, + severity: ErrorSeverity.InvalidReact, + loc: result[0].directive.loc ?? null, + suggestions: null, + }); + return Err(error); + } else if (result.length === 1) { + return Ok({ + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + }); + } else { + return Ok(null); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -477,12 +545,32 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we handle + * the error and fall back to the safest option which is to not optimize + * the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -659,25 +747,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -733,8 +827,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.unwrapOr(null) != null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md new file mode 100644 index 0000000000..364239e4e3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js new file mode 100644 index 0000000000..c30b30fe6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md new file mode 100644 index 0000000000..327adbe792 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + "use memo if(getTrue)"; + "use memo if(getFalse)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}} +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js new file mode 100644 index 0000000000..867ac8ee34 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js @@ -0,0 +1,12 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md new file mode 100644 index 0000000000..81ebd6dd9f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + "use memo if(getTrue)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js new file mode 100644 index 0000000000..97cf777a55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md new file mode 100644 index 0000000000..ec5ef238b7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md @@ -0,0 +1,42 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none" + +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + 'use memo if(getTrue)'; + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; + +``` + + +## Error + +``` + 10 | 'use memo if(getTrue)'; + 11 | const arr = []; +> 12 | useEffectWrapper(() => arr.push(foo)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (12:12) + 13 | arr.push(2); + 14 | return arr; + 15 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js new file mode 100644 index 0000000000..4d1ceb92b7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js @@ -0,0 +1,21 @@ +// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none" + +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + 'use memo if(getTrue)'; + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md new file mode 100644 index 0000000000..e071e37cb9 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// @gating @inferEffectDependencies @panicThreshold:"none" +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; + +``` + + +## Error + +``` + 8 | function Component({foo}) { + 9 | const arr = []; +> 10 | useEffectWrapper(() => arr.push(foo)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (10:10) + 11 | arr.push(2); + 12 | return arr; + 13 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js new file mode 100644 index 0000000000..651b24074f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js @@ -0,0 +1,19 @@ +// @gating @inferEffectDependencies @panicThreshold:"none" +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From 51a37c39ad4801a1f19f1bbc7adace3522a464b0 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 9 May 2025 13:14:24 -0400 Subject: [PATCH 394/422] [compiler][be] Make program traversal more readable React Compiler's program traversal logic is pretty lengthy and complex as we've added a lot of features piecemeal. `compileProgram` is 300+ lines long and has confusing control flow (defining helpers inline, invoking visitors, mutating-asts-while-iterating, mutating global `ALREADY_COMPILED` state). - Moved more stuff to `ProgramContext` - Separated `compileProgram` into a bunch of helpers Tested by syncing this stack to a Meta codebase and observing no compilation output changes (D74487851, P1806855669, P1806855379) --- .../src/Entrypoint/Imports.ts | 62 +- .../src/Entrypoint/Program.ts | 545 ++++++++++-------- .../ValidateNoUntransformedReferences.ts | 6 +- .../no-emit-lint-repro.expect.md | 0 .../{ => no-emit}/no-emit-lint-repro.js | 0 .../no-emit/retry-no-emit.expect.md | 64 ++ .../no-emit/retry-no-emit.js | 19 + .../no-emit/retry-opt-in--no-emit.expect.md | 60 ++ .../no-emit/retry-opt-in--no-emit.js | 21 + 9 files changed, 524 insertions(+), 253 deletions(-) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/{ => no-emit}/no-emit-lint-repro.expect.md (100%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/{ => no-emit}/no-emit-lint-repro.js (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 704f696b3b..d5cac921e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -18,8 +18,9 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; -import {CompilerReactTarget} from './Options'; -import {getReactCompilerRuntimeModule} from './Program'; +import {LoggerEvent, PluginOptions} from './Options'; +import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( path: NodePath, @@ -52,32 +53,61 @@ export function validateRestrictedImports( } } +type ProgramContextOptions = { + program: NodePath; + suppressions: Array; + opts: PluginOptions; + filename: string | null; + code: string | null; +}; export class ProgramContext { - /* Program and environment context */ + /** + * Program and environment context + */ scope: BabelScope; + opts: PluginOptions; + filename: string | null; + code: string | null; reactRuntimeModule: string; - hookPattern: string | null; + suppressions: Array; + /* + * This is a hack to work around what seems to be a Babel bug. Babel doesn't + * consistently respect the `skip()` function to avoid revisiting a node within + * a pass, so we use this set to track nodes that we have compiled. + */ + alreadyCompiled: WeakSet | Set = new (WeakSet ?? Set)(); // known generated or referenced identifiers in the program knownReferencedNames: Set = new Set(); // generated imports imports: Map> = new Map(); - constructor( - program: NodePath, - reactRuntimeModule: CompilerReactTarget, - hookPattern: string | null, - ) { - this.hookPattern = hookPattern; + /** + * Metadata from compilation + */ + retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; + inferredEffectLocations: Set = new Set(); + + constructor({ + program, + suppressions, + opts, + filename, + code, + }: ProgramContextOptions) { this.scope = program.scope; - this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule); + this.opts = opts; + this.filename = filename; + this.code = code; + this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); + this.suppressions = suppressions; } isHookName(name: string): boolean { - if (this.hookPattern == null) { + if (this.opts.environment.hookPattern == null) { return isHookName(name); } else { - const match = new RegExp(this.hookPattern).exec(name); + const match = new RegExp(this.opts.environment.hookPattern).exec(name); return ( match != null && typeof match[1] === 'string' && isHookName(match[1]) ); @@ -179,6 +209,12 @@ export class ProgramContext { }); return Err(error); } + + logEvent(event: LoggerEvent): void { + if (this.opts.logger != null) { + this.opts.logger.logEvent(this.filename, event); + } + } } function getExistingImports( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 34347f10b8..1a3445531d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment'; +import {ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -43,17 +43,21 @@ export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); export function findDirectiveEnablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), + ) ?? null ); } export function findDirectiveDisablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_OUT_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_OUT_DIRECTIVES.has(directive.value.value), + ) ?? null ); } @@ -88,13 +92,16 @@ export type CompileResult = { function logError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - if (pass.opts.logger) { + if (context.opts.logger) { if (err instanceof CompilerError) { for (const detail of err.details) { - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, detail: detail.options, @@ -108,7 +115,7 @@ function logError( stringifiedError = err?.toString() ?? '[ null ]'; } - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'PipelineError', fnLoc, data: stringifiedError, @@ -118,13 +125,17 @@ function logError( } function handleError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - logError(err, pass, fnLoc); + logError(err, context, fnLoc); if ( - pass.opts.panicThreshold === 'all_errors' || - (pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) || + context.opts.panicThreshold === 'all_errors' || + (context.opts.panicThreshold === 'critical_errors' && + isCriticalError(err)) || isConfigError(err) // Always throws regardless of panic threshold ) { throw err; @@ -187,7 +198,6 @@ export function createNewFunctionNode( } } // Avoid visiting the new transformed version - ALREADY_COMPILED.add(transformedFn); return transformedFn; } @@ -239,13 +249,6 @@ function insertNewOutlinedFunctionNode( } } -/* - * This is a hack to work around what seems to be a Babel bug. Babel doesn't - * consistently respect the `skip()` function to avoid revisiting a node within - * a pass, so we use this set to track nodes that we have compiled. - */ -const ALREADY_COMPILED: WeakSet | Set = new (WeakSet ?? Set)(); - const DEFAULT_ESLINT_SUPPRESSIONS = [ 'react-hooks/exhaustive-deps', 'react-hooks/rules-of-hooks', @@ -268,41 +271,43 @@ function isFilePartOfSources( return false; } -export type CompileProgramResult = { +export type CompileProgramMetadata = { retryErrors: Array<{fn: BabelFn; error: CompilerError}>; inferredEffectLocations: Set; }; /** - * `compileProgram` is directly invoked by the react-compiler babel plugin, so - * exceptions thrown by this function will fail the babel build. - * - call `handleError` if your error is recoverable. - * Unless the error is a warning / info diagnostic, compilation of a function - * / entire file should also be skipped. - * - throw an exception if the error is fatal / not recoverable. - * Examples of this are invalid compiler configs or failure to codegen outlined - * functions *after* already emitting optimized components / hooks that invoke - * the outlined functions. + * Main entrypoint for React Compiler. + * + * @param program The Babel program node to compile + * @param pass Compiler configuration and context + * @returns Compilation results or null if compilation was skipped */ export function compileProgram( program: NodePath, pass: CompilerPass, -): CompileProgramResult | null { +): CompileProgramMetadata | null { + /** + * This is directly invoked by the react-compiler babel plugin, so exceptions + * thrown by this function will fail the babel build. + * - call `handleError` if your error is recoverable. + * Unless the error is a warning / info diagnostic, compilation of a function + * / entire file should also be skipped. + * - throw an exception if the error is fatal / not recoverable. + * Examples of this are invalid compiler configs or failure to codegen outlined + * functions *after* already emitting optimized components / hooks that invoke + * the outlined functions. + */ if (shouldSkipCompilation(program, pass)) { return null; } - - const environment = pass.opts.environment; - const restrictedImportsErr = validateRestrictedImports(program, environment); + const restrictedImportsErr = validateRestrictedImports( + program, + pass.opts.environment, + ); if (restrictedImportsErr) { handleError(restrictedImportsErr, pass, null); return null; } - - const programContext = new ProgramContext( - program, - pass.opts.target, - environment.hookPattern, - ); /* * Record lint errors and critical errors as depending on Forget's config, * we may still need to run Forget's analysis on every function (even if we @@ -313,16 +318,88 @@ export function compileProgram( pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions, ); - const queue: Array<{ - kind: 'original' | 'outlined'; - fn: BabelFn; - fnType: ReactFunctionType; - }> = []; + + const programContext = new ProgramContext({ + program: program, + opts: pass.opts, + filename: pass.filename, + code: pass.code, + suppressions, + }); + + const queue: Array = findFunctionsToCompile( + program, + pass, + programContext, + ); const compiledFns: Array = []; + while (queue.length !== 0) { + const current = queue.shift()!; + const compiled = processFn(current.fn, current.fnType, programContext); + + if (compiled != null) { + for (const outlined of compiled.outlined) { + CompilerError.invariant(outlined.fn.outlined.length === 0, { + reason: 'Unexpected nested outlined functions', + loc: outlined.fn.loc, + }); + const fn = insertNewOutlinedFunctionNode( + program, + current.fn, + outlined.fn, + ); + fn.skip(); + programContext.alreadyCompiled.add(fn.node); + if (outlined.type !== null) { + queue.push({ + kind: 'outlined', + fn, + fnType: outlined.type, + }); + } + } + compiledFns.push({ + kind: current.kind, + originalFn: current.fn, + compiledFn: compiled, + }); + } + } + + // Avoid modifying the program if we find a program level opt-out + if (findDirectiveDisablingMemoization(program.node.directives) != null) { + return null; + } + + // Insert React Compiler generated functions into the Babel AST + applyCompiledFunctions(program, compiledFns, pass, programContext); + + return { + retryErrors: programContext.retryErrors, + inferredEffectLocations: programContext.inferredEffectLocations, + }; +} + +type CompileSource = { + kind: 'original' | 'outlined'; + fn: BabelFn; + fnType: ReactFunctionType; +}; +/** + * Find all React components and hooks that need to be compiled + * + * @returns An array of React functions from @param program to transform + */ +function findFunctionsToCompile( + program: NodePath, + pass: CompilerPass, + programContext: ProgramContext, +): Array { + const queue: Array = []; const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => { - const fnType = getReactFunctionType(fn, pass, environment); - if (fnType === null || ALREADY_COMPILED.has(fn.node)) { + const fnType = getReactFunctionType(fn, pass); + if (fnType === null || programContext.alreadyCompiled.has(fn.node)) { return; } @@ -331,7 +408,7 @@ export function compileProgram( * traversal will loop infinitely. * Ensure we avoid visiting the original function again. */ - ALREADY_COMPILED.add(fn.node); + programContext.alreadyCompiled.add(fn.node); fn.skip(); queue.push({kind: 'original', fn, fnType}); @@ -346,7 +423,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, ClassExpression(node: NodePath) { @@ -355,7 +431,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, FunctionDeclaration: traverseFunction, @@ -370,205 +445,205 @@ export function compileProgram( filename: pass.filename ?? null, }, ); - const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; - const inferredEffectLocations = new Set(); - const processFn = ( - fn: BabelFn, - fnType: ReactFunctionType, - ): null | CodegenFunction => { - let optInDirectives: Array = []; - let optOutDirectives: Array = []; - if (fn.node.body.type === 'BlockStatement') { - optInDirectives = findDirectiveEnablingMemoization( - fn.node.body.directives, - ); - optOutDirectives = findDirectiveDisablingMemoization( - fn.node.body.directives, - ); - } + return queue; +} - /** - * Note that Babel does not attach comment nodes to nodes; they are dangling off of the - * Program node itself. We need to figure out whether an eslint suppression range - * applies to this function first. - */ - const suppressionsInFunction = filterSuppressionsThatAffectFunction( - suppressions, - fn, - ); - let compileResult: - | {kind: 'compile'; compiledFn: CodegenFunction} - | {kind: 'error'; error: unknown}; - if (suppressionsInFunction.length > 0) { - compileResult = { - kind: 'error', - error: suppressionsToCompilerError(suppressionsInFunction), - }; +/** + * Try to compile a source function, taking into account all local suppressions, + * opt-ins, and opt-outs. + * + * Errors encountered during compilation are either logged (if recoverable) or + * thrown (if non-recoverable). + * + * @returns the compiled function or null if the function was skipped (due to + * config settings and/or outputs) + */ +function processFn( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): null | CodegenFunction { + let directives; + if (fn.node.body.type !== 'BlockStatement') { + directives = {optIn: null, optOut: null}; + } else { + directives = { + optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + }; + } + + let compiledFn: CodegenFunction; + const compileResult = tryCompileFunction(fn, fnType, programContext); + if (compileResult.kind === 'error') { + if (directives.optOut != null) { + logError(compileResult.error, programContext, fn.node.loc ?? null); } else { - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'all_features', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - } catch (err) { - compileResult = {kind: 'error', error: err}; - } + handleError(compileResult.error, programContext, fn.node.loc ?? null); } - - if (compileResult.kind === 'error') { - /** - * If an opt out directive is present, log only instead of throwing and don't mark as - * containing a critical error. - */ - if (optOutDirectives.length > 0) { - logError(compileResult.error, pass, fn.node.loc ?? null); - } else { - handleError(compileResult.error, pass, fn.node.loc ?? null); - } - // If non-memoization features are enabled, retry regardless of error kind - if ( - !(environment.enableFire || environment.inferEffectDependencies != null) - ) { - return null; - } - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'no_inferred_memo', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - if ( - !compileResult.compiledFn.hasFireRewrite && - !compileResult.compiledFn.hasInferredEffect - ) { - return null; - } - } catch (err) { - // TODO: we might want to log error here, but this will also result in duplicate logging - if (err instanceof CompilerError) { - retryErrors.push({fn, error: err}); - } - return null; - } - } - - /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. - */ - if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) { - for (const directive of optOutDirectives) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSkip', - fnLoc: fn.node.body.loc ?? null, - reason: `Skipped due to '${directive.value.value}' directive.`, - loc: directive.loc ?? null, - }); - } + const retryResult = retryCompileFunction(fn, fnType, programContext); + if (retryResult == null) { return null; } + compiledFn = retryResult; + } else { + compiledFn = compileResult.compiledFn; + } - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSuccess', - fnLoc: fn.node.loc ?? null, - fnName: compileResult.compiledFn.id?.name ?? null, - memoSlots: compileResult.compiledFn.memoSlotsUsed, - memoBlocks: compileResult.compiledFn.memoBlocks, - memoValues: compileResult.compiledFn.memoValues, - prunedMemoBlocks: compileResult.compiledFn.prunedMemoBlocks, - prunedMemoValues: compileResult.compiledFn.prunedMemoValues, + /** + * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler + * for validation but we don't mutate the babel AST. This allows us to flag if there is an + * unused 'use no forget/memo' directive. + */ + if ( + programContext.opts.ignoreUseNoForget === false && + directives.optOut != null + ) { + programContext.logEvent({ + kind: 'CompileSkip', + fnLoc: fn.node.body.loc ?? null, + reason: `Skipped due to '${directives.optOut.value}' directive.`, + loc: directives.optOut.loc ?? null, }); + return null; + } + programContext.logEvent({ + kind: 'CompileSuccess', + fnLoc: fn.node.loc ?? null, + fnName: compiledFn.id?.name ?? null, + memoSlots: compiledFn.memoSlotsUsed, + memoBlocks: compiledFn.memoBlocks, + memoValues: compiledFn.memoValues, + prunedMemoBlocks: compiledFn.prunedMemoBlocks, + prunedMemoValues: compiledFn.prunedMemoValues, + }); + /** + * Always compile functions with opt in directives. + */ + if (directives.optIn != null) { + return compiledFn; + } else if (programContext.opts.compilationMode === 'annotation') { /** - * Always compile functions with opt in directives. + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. */ - if (optInDirectives.length > 0) { - return compileResult.compiledFn; - } else if (pass.opts.compilationMode === 'annotation') { - /** - * No opt-in directive in annotation mode, so don't insert the compiled function. - */ - return null; - } - - if (!pass.opts.noEmit) { - return compileResult.compiledFn; - } + return null; + } else if (programContext.opts.noEmit) { /** * inferEffectDependencies + noEmit is currently only used for linting. In * this mode, add source locations for where the compiler *can* infer effect * dependencies. */ - for (const loc of compileResult.compiledFn.inferredEffectLocations) { - if (loc !== GeneratedSource) inferredEffectLocations.add(loc); - } - return null; - }; - - while (queue.length !== 0) { - const current = queue.shift()!; - const compiled = processFn(current.fn, current.fnType); - if (compiled === null) { - continue; - } - for (const outlined of compiled.outlined) { - CompilerError.invariant(outlined.fn.outlined.length === 0, { - reason: 'Unexpected nested outlined functions', - loc: outlined.fn.loc, - }); - const fn = insertNewOutlinedFunctionNode( - program, - current.fn, - outlined.fn, - ); - fn.skip(); - ALREADY_COMPILED.add(fn.node); - if (outlined.type !== null) { - queue.push({ - kind: 'outlined', - fn, - fnType: outlined.type, - }); + for (const loc of compiledFn.inferredEffectLocations) { + if (loc !== GeneratedSource) { + programContext.inferredEffectLocations.add(loc); } } - compiledFns.push({ - kind: current.kind, - compiledFn: compiled, - originalFn: current.fn, - }); + return null; + } else { + return compiledFn; + } +} + +function tryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): + | {kind: 'compile'; compiledFn: CodegenFunction} + | {kind: 'error'; error: unknown} { + /** + * Note that Babel does not attach comment nodes to nodes; they are dangling off of the + * Program node itself. We need to figure out whether an eslint suppression range + * applies to this function first. + */ + const suppressionsInFunction = filterSuppressionsThatAffectFunction( + programContext.suppressions, + fn, + ); + if (suppressionsInFunction.length > 0) { + return { + kind: 'error', + error: suppressionsToCompilerError(suppressionsInFunction), + }; } - /** - * Do not modify source if there is a module scope level opt out directive. - */ - const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization( - program.node.directives, - ); - if (moduleScopeOptOutDirectives.length > 0) { + try { + return { + kind: 'compile', + compiledFn: compileFn( + fn, + programContext.opts.environment, + fnType, + 'all_features', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ), + }; + } catch (err) { + return {kind: 'error', error: err}; + } +} + +/** + * If non-memo feature flags are enabled, retry compilation with a more minimal + * feature set. + * + * @returns a CodegenFunction if retry was successful + */ +function retryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): CodegenFunction | null { + const environment = programContext.opts.environment; + if ( + !(environment.enableFire || environment.inferEffectDependencies != null) + ) { return null; } - /* - * Only insert Forget-ified functions if we have not encountered a critical - * error elsewhere in the file, regardless of bailout mode. + /** + * Note that function suppressions are not checked in the retry pipeline, as + * they only affect auto-memoization features. */ + try { + const retryResult = compileFn( + fn, + environment, + fnType, + 'no_inferred_memo', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ); + + if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) { + return null; + } + return retryResult; + } catch (err) { + // TODO: we might want to log error here, but this will also result in duplicate logging + if (err instanceof CompilerError) { + programContext.retryErrors.push({fn, error: err}); + } + return null; + } +} + +/** + * Applies React Compiler generated functions to the babel AST by replacing + * existing functions in place or inserting new declarations. + */ +function applyCompiledFunctions( + program: NodePath, + compiledFns: Array, + pass: CompilerPass, + programContext: ProgramContext, +): void { const referencedBeforeDeclared = pass.opts.gating != null ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) @@ -576,6 +651,7 @@ export function compileProgram( for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); + programContext.alreadyCompiled.add(transformedFn); if (referencedBeforeDeclared != null && kind === 'original') { CompilerError.invariant(pass.opts.gating != null, { @@ -598,7 +674,6 @@ export function compileProgram( if (compiledFns.length > 0) { addImportsToProgram(program, programContext); } - return {retryErrors, inferredEffectLocations}; } function shouldSkipCompilation( @@ -640,14 +715,10 @@ function shouldSkipCompilation( function getReactFunctionType( fn: BabelFn, pass: CompilerPass, - /** - * TODO(mofeiZ): remove once we validate PluginOptions with Zod - */ - environment: EnvironmentConfig, ): ReactFunctionType | null { - const hookPattern = environment.hookPattern; + const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0) + if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 5f6c6986e0..e288c227ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -18,7 +18,7 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {Environment} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; -import {CompileProgramResult} from './Program'; +import {CompileProgramMetadata} from './Program'; function throwInvalidReact( options: Omit, @@ -109,7 +109,7 @@ export default function validateNoUntransformedReferences( filename: string | null, logger: Logger | null, env: EnvironmentConfig, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const moduleLoadChecks = new Map< string, @@ -236,7 +236,7 @@ function transformProgram( moduleLoadChecks: Map>, filename: string | null, logger: Logger | null, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const traversalState: TraversalState = { shouldInvalidateScopes: true, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md new file mode 100644 index 0000000000..bd70c0138d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo({ propVal }) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js new file mode 100644 index 0000000000..d1dda06a04 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js @@ -0,0 +1,19 @@ +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md new file mode 100644 index 0000000000..8d2e5c7c31 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md @@ -0,0 +1,60 @@ + +## Input + +```javascript +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + 'use memo'; + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo(t0) { + "use memo"; + const { propVal } = t0; + + const arr = [propVal]; + useEffectWrapper(() => print(arr), [arr]); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal), [arr2, propVal]); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js new file mode 100644 index 0000000000..2650cbe5d7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js @@ -0,0 +1,21 @@ +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + 'use memo'; + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; From 9e6b43041b455878f754341e3c82d05c25b71c79 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 9 May 2025 13:14:24 -0400 Subject: [PATCH 395/422] [compiler][entrypoint] Fix edgecases for noEmit and opt-outs Title --- .../src/Entrypoint/Imports.ts | 4 ++ .../src/Entrypoint/Program.ts | 43 +++++++++++++------ .../no-emit/retry-opt-in--no-emit.expect.md | 9 ++-- ...bailout-nopanic-shouldnt-outline.expect.md | 3 -- .../compiler/use-memo-noemit.expect.md | 15 +------ 5 files changed, 39 insertions(+), 35 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index d5cac921e9..24ce37cf72 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -59,6 +59,7 @@ type ProgramContextOptions = { opts: PluginOptions; filename: string | null; code: string | null; + hasModuleScopeOptOut: boolean; }; export class ProgramContext { /** @@ -70,6 +71,7 @@ export class ProgramContext { code: string | null; reactRuntimeModule: string; suppressions: Array; + hasModuleScopeOptOut: boolean; /* * This is a hack to work around what seems to be a Babel bug. Babel doesn't @@ -94,6 +96,7 @@ export class ProgramContext { opts, filename, code, + hasModuleScopeOptOut, }: ProgramContextOptions) { this.scope = program.scope; this.opts = opts; @@ -101,6 +104,7 @@ export class ProgramContext { this.code = code; this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); this.suppressions = suppressions; + this.hasModuleScopeOptOut = hasModuleScopeOptOut; } isHookName(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 1a3445531d..64abc110ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -325,6 +325,8 @@ export function compileProgram( filename: pass.filename, code: pass.code, suppressions, + hasModuleScopeOptOut: + findDirectiveDisablingMemoization(program.node.directives) != null, }); const queue: Array = findFunctionsToCompile( @@ -368,7 +370,19 @@ export function compileProgram( } // Avoid modifying the program if we find a program level opt-out - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } return null; } @@ -491,9 +505,10 @@ function processFn( } /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. + * If 'use no forget/memo' is present and we still ran the code through the + * compiler for validation, log a skip event and don't mutate the babel AST. + * This allows us to flag if there is an unused 'use no forget/memo' + * directive. */ if ( programContext.opts.ignoreUseNoForget === false && @@ -518,16 +533,7 @@ function processFn( prunedMemoValues: compiledFn.prunedMemoValues, }); - /** - * Always compile functions with opt in directives. - */ - if (directives.optIn != null) { - return compiledFn; - } else if (programContext.opts.compilationMode === 'annotation') { - /** - * If no opt-in directive is found and the compiler is configured in - * annotation mode, don't insert the compiled function. - */ + if (programContext.hasModuleScopeOptOut) { return null; } else if (programContext.opts.noEmit) { /** @@ -541,6 +547,15 @@ function processFn( } } return null; + } else if ( + programContext.opts.compilationMode === 'annotation' && + directives.optIn == null + ) { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; } else { return compiledFn; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md index 8d2e5c7c31..6e4ddeeb2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md @@ -33,16 +33,15 @@ export const FIXTURE_ENTRYPOINT = { import { print } from "shared-runtime"; import useEffectWrapper from "useEffectWrapper"; -function Foo(t0) { +function Foo({ propVal }) { "use memo"; - const { propVal } = t0; - const arr = [propVal]; - useEffectWrapper(() => print(arr), [arr]); + useEffectWrapper(() => print(arr)); const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), [arr2, propVal]); + useEffectWrapper(() => arr2.push(propVal)); arr2.push(2); + return { arr, arr2 }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md index f24d949205..cfbaa34568 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -20,9 +20,6 @@ function Foo() { function Foo() { return ; } -function _temp() { - return alert("hello!"); -} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md index dfc831555f..c47501945b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -19,22 +19,11 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @noEmit +// @noEmit function Foo() { "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return alert("hello!"); + return ; } export const FIXTURE_ENTRYPOINT = { From db2c089b9fff1e874888d655e353cdba06cafb82 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 9 May 2025 13:14:24 -0400 Subject: [PATCH 396/422] [compiler][gating] Experimental directive based gating Adds `dynamicGating` as an experimental option for testing rollout DX at Meta. If specified, this enables dynamic gating which matches `use memo if(...)` directives. #### Example usage Input file ```js // @dynamicGating:{"source":"myModule"} export function MyComponent() { 'use memo if(isEnabled)'; return
...
; } ``` Compiler output ```js import {isEnabled} from 'myModule'; export const MyComponent = isEnabled() ? : ; ``` --- .../src/Entrypoint/Options.ts | 46 ++++++ .../src/Entrypoint/Program.ts | 143 +++++++++++++++--- .../dynamic-gating-annotation.expect.md | 50 ++++++ .../gating/dynamic-gating-annotation.js | 11 ++ .../dynamic-gating-bailout-nopanic.expect.md | 66 ++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 ++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 ++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ .../dynamic-gating-invalid-multiple.expect.md | 45 ++++++ .../gating/dynamic-gating-invalid-multiple.js | 12 ++ .../gating/dynamic-gating-noemit.expect.md | 37 +++++ .../compiler/gating/dynamic-gating-noemit.js | 11 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 ++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../error.todo-dynamic-gating.expect.md | 42 +++++ .../error.todo-dynamic-gating.js | 21 +++ .../bailout-retry/error.todo-gating.expect.md | 40 +++++ .../bailout-retry/error.todo-gating.js | 19 +++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 + 26 files changed, 814 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..96cce887d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,28 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + * ``` + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +270,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +319,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 64abc110ea..cb57bd2c49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,64 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else if (result.length > 1) { + const error = new CompilerError(); + error.push({ + reason: `Multiple dynamic gating directives found`, + description: `Expected a single directive but found [${result + .map(r => r.directive.value.value) + .join(', ')}]`, + severity: ErrorSeverity.InvalidReact, + loc: result[0].directive.loc ?? null, + suggestions: null, + }); + return Err(error); + } else if (result.length === 1) { + return Ok({ + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + }); + } else { + return Ok(null); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -477,12 +545,32 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we handle + * the error and fall back to the safest option which is to not optimize + * the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -659,25 +747,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -733,8 +827,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.unwrapOr(null) != null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md new file mode 100644 index 0000000000..364239e4e3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js new file mode 100644 index 0000000000..c30b30fe6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md new file mode 100644 index 0000000000..327adbe792 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + "use memo if(getTrue)"; + "use memo if(getFalse)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}} +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js new file mode 100644 index 0000000000..867ac8ee34 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js @@ -0,0 +1,12 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md new file mode 100644 index 0000000000..81ebd6dd9f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + "use memo if(getTrue)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js new file mode 100644 index 0000000000..97cf777a55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md new file mode 100644 index 0000000000..ec5ef238b7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md @@ -0,0 +1,42 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none" + +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + 'use memo if(getTrue)'; + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; + +``` + + +## Error + +``` + 10 | 'use memo if(getTrue)'; + 11 | const arr = []; +> 12 | useEffectWrapper(() => arr.push(foo)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (12:12) + 13 | arr.push(2); + 14 | return arr; + 15 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js new file mode 100644 index 0000000000..4d1ceb92b7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js @@ -0,0 +1,21 @@ +// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none" + +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + 'use memo if(getTrue)'; + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md new file mode 100644 index 0000000000..e071e37cb9 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// @gating @inferEffectDependencies @panicThreshold:"none" +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; + +``` + + +## Error + +``` + 8 | function Component({foo}) { + 9 | const arr = []; +> 10 | useEffectWrapper(() => arr.push(foo)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (10:10) + 11 | arr.push(2); + 12 | return arr; + 13 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js new file mode 100644 index 0000000000..651b24074f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js @@ -0,0 +1,19 @@ +// @gating @inferEffectDependencies @panicThreshold:"none" +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From 1b658717e901b18050fe647ac18bcbc099380219 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 9 May 2025 13:30:42 -0400 Subject: [PATCH 397/422] [compiler][entrypoint] Fix edgecases for noEmit and opt-outs Title --- .../src/Entrypoint/Imports.ts | 4 ++ .../src/Entrypoint/Program.ts | 43 +++++++++++++------ .../no-emit/retry-opt-in--no-emit.expect.md | 9 ++-- ...bailout-nopanic-shouldnt-outline.expect.md | 3 -- .../compiler/use-memo-noemit.expect.md | 15 +------ 5 files changed, 39 insertions(+), 35 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index d5cac921e9..24ce37cf72 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -59,6 +59,7 @@ type ProgramContextOptions = { opts: PluginOptions; filename: string | null; code: string | null; + hasModuleScopeOptOut: boolean; }; export class ProgramContext { /** @@ -70,6 +71,7 @@ export class ProgramContext { code: string | null; reactRuntimeModule: string; suppressions: Array; + hasModuleScopeOptOut: boolean; /* * This is a hack to work around what seems to be a Babel bug. Babel doesn't @@ -94,6 +96,7 @@ export class ProgramContext { opts, filename, code, + hasModuleScopeOptOut, }: ProgramContextOptions) { this.scope = program.scope; this.opts = opts; @@ -101,6 +104,7 @@ export class ProgramContext { this.code = code; this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); this.suppressions = suppressions; + this.hasModuleScopeOptOut = hasModuleScopeOptOut; } isHookName(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 1a3445531d..64abc110ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -325,6 +325,8 @@ export function compileProgram( filename: pass.filename, code: pass.code, suppressions, + hasModuleScopeOptOut: + findDirectiveDisablingMemoization(program.node.directives) != null, }); const queue: Array = findFunctionsToCompile( @@ -368,7 +370,19 @@ export function compileProgram( } // Avoid modifying the program if we find a program level opt-out - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } return null; } @@ -491,9 +505,10 @@ function processFn( } /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. + * If 'use no forget/memo' is present and we still ran the code through the + * compiler for validation, log a skip event and don't mutate the babel AST. + * This allows us to flag if there is an unused 'use no forget/memo' + * directive. */ if ( programContext.opts.ignoreUseNoForget === false && @@ -518,16 +533,7 @@ function processFn( prunedMemoValues: compiledFn.prunedMemoValues, }); - /** - * Always compile functions with opt in directives. - */ - if (directives.optIn != null) { - return compiledFn; - } else if (programContext.opts.compilationMode === 'annotation') { - /** - * If no opt-in directive is found and the compiler is configured in - * annotation mode, don't insert the compiled function. - */ + if (programContext.hasModuleScopeOptOut) { return null; } else if (programContext.opts.noEmit) { /** @@ -541,6 +547,15 @@ function processFn( } } return null; + } else if ( + programContext.opts.compilationMode === 'annotation' && + directives.optIn == null + ) { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; } else { return compiledFn; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md index 8d2e5c7c31..6e4ddeeb2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md @@ -33,16 +33,15 @@ export const FIXTURE_ENTRYPOINT = { import { print } from "shared-runtime"; import useEffectWrapper from "useEffectWrapper"; -function Foo(t0) { +function Foo({ propVal }) { "use memo"; - const { propVal } = t0; - const arr = [propVal]; - useEffectWrapper(() => print(arr), [arr]); + useEffectWrapper(() => print(arr)); const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), [arr2, propVal]); + useEffectWrapper(() => arr2.push(propVal)); arr2.push(2); + return { arr, arr2 }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md index f24d949205..cfbaa34568 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -20,9 +20,6 @@ function Foo() { function Foo() { return ; } -function _temp() { - return alert("hello!"); -} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md index dfc831555f..c47501945b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -19,22 +19,11 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @noEmit +// @noEmit function Foo() { "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return alert("hello!"); + return ; } export const FIXTURE_ENTRYPOINT = { From ab752bf7a1824d2d2075f02534b4440edf1f4e2e Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 9 May 2025 13:30:42 -0400 Subject: [PATCH 398/422] [compiler][gating] Experimental directive based gating Adds `dynamicGating` as an experimental option for testing rollout DX at Meta. If specified, this enables dynamic gating which matches `use memo if(...)` directives. #### Example usage Input file ```js // @dynamicGating:{"source":"myModule"} export function MyComponent() { 'use memo if(isEnabled)'; return
...
; } ``` Compiler output ```js import {isEnabled} from 'myModule'; export const MyComponent = isEnabled() ? : ; ``` --- .../src/Entrypoint/Options.ts | 46 ++++++ .../src/Entrypoint/Program.ts | 143 +++++++++++++++--- .../dynamic-gating-annotation.expect.md | 50 ++++++ .../gating/dynamic-gating-annotation.js | 11 ++ .../dynamic-gating-bailout-nopanic.expect.md | 66 ++++++++ .../gating/dynamic-gating-bailout-nopanic.js | 22 +++ .../gating/dynamic-gating-disabled.expect.md | 50 ++++++ .../gating/dynamic-gating-disabled.js | 11 ++ .../gating/dynamic-gating-enabled.expect.md | 50 ++++++ .../compiler/gating/dynamic-gating-enabled.js | 11 ++ ...ating-invalid-identifier-nopanic.expect.md | 37 +++++ ...namic-gating-invalid-identifier-nopanic.js | 11 ++ .../dynamic-gating-invalid-multiple.expect.md | 45 ++++++ .../gating/dynamic-gating-invalid-multiple.js | 12 ++ .../gating/dynamic-gating-noemit.expect.md | 37 +++++ .../compiler/gating/dynamic-gating-noemit.js | 11 ++ ...ntifier-nopanic-required-feature.expect.md | 35 +++++ ...lid-identifier-nopanic-required-feature.js | 14 ++ ...ynamic-gating-invalid-identifier.expect.md | 32 ++++ ...error.dynamic-gating-invalid-identifier.js | 11 ++ .../error.todo-dynamic-gating.expect.md | 42 +++++ .../error.todo-dynamic-gating.js | 21 +++ .../bailout-retry/error.todo-gating.expect.md | 40 +++++ .../bailout-retry/error.todo-gating.js | 19 +++ .../babel-plugin-react-compiler/src/index.ts | 2 +- .../snap/src/sprout/shared-runtime.ts | 8 + 26 files changed, 814 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c732e16410..96cce887d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -37,6 +37,10 @@ const PanicThresholdOptionsSchema = z.enum([ ]); export type PanicThresholdOptions = z.infer; +const DynamicGatingOptionsSchema = z.object({ + source: z.string(), +}); +export type DynamicGatingOptions = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -65,6 +69,28 @@ export type PluginOptions = { */ gating: ExternalFunction | null; + /** + * If specified, this enables dynamic gating which matches `use memo if(...)` + * directives. + * + * Example usage: + * ```js + * // @dynamicGating:{"source":"myModule"} + * export function MyComponent() { + * 'use memo if(isEnabled)'; + * return
...
; + * } + * ``` + * This will emit: + * ```js + * import {isEnabled} from 'myModule'; + * export const MyComponent = isEnabled() + * ? + * : ; + * ``` + */ + dynamicGating: DynamicGatingOptions | null; + panicThreshold: PanicThresholdOptions; /* @@ -244,6 +270,7 @@ export const defaultOptions: PluginOptions = { logger: null, gating: null, noEmit: false, + dynamicGating: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -292,6 +319,25 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'dynamicGating': { + if (value == null) { + parsedOptions[key] = null; + } else { + const result = DynamicGatingOptionsSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse dynamic gating. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 64abc110ea..cb57bd2c49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {ReactFunctionType} from '../HIR/Environment'; +import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -31,6 +31,7 @@ import { suppressionsToCompilerError, } from './Suppression'; import {GeneratedSource} from '../HIR'; +import {Err, Ok, Result} from '../Utils/Result'; export type CompilerPass = { opts: PluginOptions; @@ -40,15 +41,24 @@ export type CompilerPass = { }; export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); +const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$'); -export function findDirectiveEnablingMemoization( +export function tryFindDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - return ( - directives.find(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), - ) ?? null + opts: PluginOptions, +): Result { + const optIn = directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), ); + if (optIn != null) { + return Ok(optIn); + } + const dynamicGating = findDirectivesDynamicGating(directives, opts); + if (dynamicGating.isOk()) { + return Ok(dynamicGating.unwrap()?.directive ?? null); + } else { + return Err(dynamicGating.unwrapErr()); + } } export function findDirectiveDisablingMemoization( @@ -60,6 +70,64 @@ export function findDirectiveDisablingMemoization( ) ?? null ); } +function findDirectivesDynamicGating( + directives: Array, + opts: PluginOptions, +): Result< + { + gating: ExternalFunction; + directive: t.Directive; + } | null, + CompilerError +> { + if (opts.dynamicGating === null) { + return Ok(null); + } + const errors = new CompilerError(); + const result: Array<{directive: t.Directive; match: string}> = []; + + for (const directive of directives) { + const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value); + if (maybeMatch != null && maybeMatch[1] != null) { + if (t.isValidIdentifier(maybeMatch[1])) { + result.push({directive, match: maybeMatch[1]}); + } else { + errors.push({ + reason: `Dynamic gating directive is not a valid JavaScript identifier`, + description: `Found '${directive.value.value}'`, + severity: ErrorSeverity.InvalidReact, + loc: directive.loc ?? null, + suggestions: null, + }); + } + } + } + if (errors.hasErrors()) { + return Err(errors); + } else if (result.length > 1) { + const error = new CompilerError(); + error.push({ + reason: `Multiple dynamic gating directives found`, + description: `Expected a single directive but found [${result + .map(r => r.directive.value.value) + .join(', ')}]`, + severity: ErrorSeverity.InvalidReact, + loc: result[0].directive.loc ?? null, + suggestions: null, + }); + return Err(error); + } else if (result.length === 1) { + return Ok({ + gating: { + source: opts.dynamicGating.source, + importSpecifierName: result[0].match, + }, + directive: result[0].directive, + }); + } else { + return Ok(null); + } +} function isCriticalError(err: unknown): boolean { return !(err instanceof CompilerError) || err.isCritical(); @@ -477,12 +545,32 @@ function processFn( fnType: ReactFunctionType, programContext: ProgramContext, ): null | CodegenFunction { - let directives; + let directives: { + optIn: t.Directive | null; + optOut: t.Directive | null; + }; if (fn.node.body.type !== 'BlockStatement') { - directives = {optIn: null, optOut: null}; - } else { directives = { - optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optIn: null, + optOut: null, + }; + } else { + const optIn = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + programContext.opts, + ); + if (optIn.isErr()) { + /** + * If parsing opt-in directive fails, it's most likely that React Compiler + * was not tested or rolled out on this function. In that case, we handle + * the error and fall back to the safest option which is to not optimize + * the function. + */ + handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null); + return null; + } + directives = { + optIn: optIn.unwrapOr(null), optOut: findDirectiveDisablingMemoization(fn.node.body.directives), }; } @@ -659,25 +747,31 @@ function applyCompiledFunctions( pass: CompilerPass, programContext: ProgramContext, ): void { - const referencedBeforeDeclared = - pass.opts.gating != null - ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) - : null; + let referencedBeforeDeclared = null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); programContext.alreadyCompiled.add(transformedFn); - if (referencedBeforeDeclared != null && kind === 'original') { - CompilerError.invariant(pass.opts.gating != null, { - reason: "Expected 'gating' import to be present", - loc: null, - }); + let dynamicGating: ExternalFunction | null = null; + if (originalFn.node.body.type === 'BlockStatement') { + const result = findDirectivesDynamicGating( + originalFn.node.body.directives, + pass.opts, + ); + if (result.isOk()) { + dynamicGating = result.unwrap()?.gating ?? null; + } + } + const functionGating = dynamicGating ?? pass.opts.gating; + if (kind === 'original' && functionGating != null) { + referencedBeforeDeclared ??= + getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns); insertGatedFunctionDeclaration( originalFn, transformedFn, programContext, - pass.opts.gating, + functionGating, referencedBeforeDeclared.has(result), ); } else { @@ -733,8 +827,13 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) + const optInDirectives = tryFindDirectiveEnablingMemoization( + fn.node.body.directives, + pass.opts, + ); + if (optInDirectives.unwrapOr(null) != null) { return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + } } // Component and hook declarations are known components/hooks diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md new file mode 100644 index 0000000000..364239e4e3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js new file mode 100644 index 0000000000..c30b30fe6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-annotation.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation" + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md new file mode 100644 index 0000000000..dc3cc2b98d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import { useMemo } from "react"; +import { identity } from "shared-runtime"; + +function Foo({ value }) { + "use memo if(getTrue)"; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 1 }], + sequentialRenders: [{ value: 1 }, { value: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"reason":"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","description":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}} +``` + +### Eval output +(kind: ok)
initial value 1
current value 1
+
initial value 1
current value 2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js new file mode 100644 index 0000000000..ceddbefdd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.js @@ -0,0 +1,22 @@ +// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly + +import {useMemo} from 'react'; +import {identity} from 'shared-runtime'; + +function Foo({value}) { + 'use memo if(getTrue)'; + + const initialValue = useMemo(() => identity(value), []); + return ( + <> +
initial value {initialValue}
+
current value {value}
+ + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 1}], + sequentialRenders: [{value: 1}, {value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md new file mode 100644 index 0000000000..7d95b54317 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getFalse() + ? function Foo() { + "use memo if(getFalse)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getFalse)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js new file mode 100644 index 0000000000..be29f10568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-disabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md new file mode 100644 index 0000000000..272c5a5714 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} +const Foo = getTrue() + ? function Foo() { + "use memo if(getTrue)"; + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; + } + : function Foo() { + "use memo if(getTrue)"; + return
hello world
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js new file mode 100644 index 0000000000..9280e25d11 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-enabled.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md new file mode 100644 index 0000000000..c8c91910b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + "use memo if(true)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js new file mode 100644 index 0000000000..4d0d9c3bb8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-identifier-nopanic.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md new file mode 100644 index 0000000000..327adbe792 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + "use memo if(getTrue)"; + "use memo if(getFalse)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}} +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js new file mode 100644 index 0000000000..867ac8ee34 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.js @@ -0,0 +1,12 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly + +function Foo() { + 'use memo if(getTrue)'; + 'use memo if(getFalse)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md new file mode 100644 index 0000000000..81ebd6dd9f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +## Code + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + "use memo if(getTrue)"; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
hello world
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js new file mode 100644 index 0000000000..97cf777a55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-noemit.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} @noEmit + +function Foo() { + 'use memo if(getTrue)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md new file mode 100644 index 0000000000..7f9f608383 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; + +``` + + +## Error + +``` + 6 | 'use memo if(invalid identifier)'; + 7 | const arr = [propVal]; +> 8 | useEffect(() => print(arr)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8: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/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js new file mode 100644 index 0000000000..7d5b74acc7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js @@ -0,0 +1,14 @@ +// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + 'use memo if(invalid identifier)'; + const arr = [propVal]; + useEffect(() => print(arr)); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveVariable, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md new file mode 100644 index 0000000000..c824afd680 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; + +``` + + +## Error + +``` + 2 | + 3 | function Foo() { +> 4 | 'use memo if(true)'; + | ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4) + 5 | return
hello world
; + 6 | } + 7 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js new file mode 100644 index 0000000000..c400554497 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier.js @@ -0,0 +1,11 @@ +// @dynamicGating:{"source":"shared-runtime"} + +function Foo() { + 'use memo if(true)'; + return
hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md new file mode 100644 index 0000000000..ec5ef238b7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md @@ -0,0 +1,42 @@ + +## Input + +```javascript +// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none" + +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + 'use memo if(getTrue)'; + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; + +``` + + +## Error + +``` + 10 | 'use memo if(getTrue)'; + 11 | const arr = []; +> 12 | useEffectWrapper(() => arr.push(foo)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (12:12) + 13 | arr.push(2); + 14 | return arr; + 15 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js new file mode 100644 index 0000000000..4d1ceb92b7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js @@ -0,0 +1,21 @@ +// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none" + +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + 'use memo if(getTrue)'; + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md new file mode 100644 index 0000000000..e071e37cb9 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// @gating @inferEffectDependencies @panicThreshold:"none" +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; + +``` + + +## Error + +``` + 8 | function Component({foo}) { + 9 | const arr = []; +> 10 | useEffectWrapper(() => arr.push(foo)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (10:10) + 11 | arr.push(2); + 12 | return arr; + 13 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js new file mode 100644 index 0000000000..651b24074f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js @@ -0,0 +1,19 @@ +// @gating @inferEffectDependencies @panicThreshold:"none" +import useEffectWrapper from 'useEffectWrapper'; + +/** + * TODO: run the non-forget enabled version through the effect inference + * pipeline. + */ +function Component({foo}) { + const arr = []; + useEffectWrapper(() => arr.push(foo)); + arr.push(2); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], + sequentialRenders: [{foo: 1}, {foo: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 086e010fea..cbae672e50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,7 +20,7 @@ export { OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, ProgramContext, - findDirectiveEnablingMemoization, + tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue, type Logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 1b8648f4ff..569d31cbd4 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -128,6 +128,14 @@ export function getNull(): null { return null; } +export function getTrue(): true { + return true; +} + +export function getFalse(): false { + return false; +} + export function calculateExpensiveNumber(x: number): number { return x; } From e9740c3530bb1817f305456bf9b5b89729aa8347 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 14:10:04 -0400 Subject: [PATCH 399/422] [compiler] Inferred effect dependencies now include optional chains Inferred effect dependencies now include optional chains. This is a temporary solution while https://github.com/facebook/react/pull/32099 and its followups are worked on. Ideally, we should model reactive scope dependencies in the IR similarly to `ComputeIR` -- dependencies should be hoisted and all references rewritten to use the hoisted dependencies. (will add more test cases) --- .../src/HIR/ScopeDependencyUtils.ts | 280 +++++++++++++++++ .../src/Inference/InferEffectDependencies.ts | 281 ++++++++++++------ .../reactive-optional-chain.expect.md | 31 +- .../reactive-optional-chain.js | 8 +- 4 files changed, 500 insertions(+), 100 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts new file mode 100644 index 0000000000..0fe55a2b22 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts @@ -0,0 +1,280 @@ +import { + Place, + ReactiveScopeDependency, + Identifier, + makeInstructionId, + InstructionKind, + GeneratedSource, + BlockId, + makeTemporaryIdentifier, + Effect, + GotoVariant, + HIR, +} from './HIR'; +import {CompilerError} from '../CompilerError'; +import {Environment} from './Environment'; +import HIRBuilder from './HIRBuilder'; +import {lowerValueToTemporary} from './BuildHIR'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; + +type DependencyInstructions = { + place: Place; + value: HIR; + exitBlockId: BlockId; +}; + +export function buildDependencyInstructions( + dep: ReactiveScopeDependency, + env: Environment, +): DependencyInstructions { + const builder = new HIRBuilder(env, { + entryBlockKind: 'value', + }); + let result: Place; + if (dep.path.every(path => !path.optional)) { + const last = writeNonOptionalDependency(dep, env, builder); + result = { + kind: 'Identifier', + identifier: last, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }; + } else { + const last = writeOptionalDependency( + dep.path.length - 1, + dep, + builder, + null, + ); + result = { + kind: 'Identifier', + identifier: last, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }; + } + + const exitBlockId = builder.terminate( + { + kind: 'unsupported', + loc: GeneratedSource, + id: makeInstructionId(0), + }, + null, + ); + return { + place: result, + value: builder.build(), + exitBlockId, + }; +} +export function writeNonOptionalDependency( + dep: ReactiveScopeDependency, + env: Environment, + builder: HIRBuilder, +): Identifier { + const loc = dep.identifier.loc; + let last: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: last, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'LoadLocal', + place: { + identifier: dep.identifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + + for (const path of dep.path) { + const next = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: next, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'PropertyLoad', + object: { + identifier: last, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + property: path.property, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + last = next; + } + return last; +} + +export function writeOptionalDependency( + idx: number, + dep: ReactiveScopeDependency, + builder: HIRBuilder, + parentAlternate: BlockId | null, +): Identifier { + const env = builder.environment; + CompilerError.invariant( + idx >= 0 && !dep.path.slice(0, idx + 1).every(path => !path.optional), + { + reason: '[WriteOptional] Expected optional path', + description: `${idx} ${printDependency(dep)}`, + loc: GeneratedSource, + }, + ); + const continuationBlock = builder.reserve(builder.currentBlockKind()); + const consequent = builder.reserve('value'); + + const returnPlace: Place = { + kind: 'Identifier', + identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource), + effect: Effect.Mutate, + reactive: dep.reactive, + loc: GeneratedSource, + }; + + let alternate; + if (parentAlternate != null) { + alternate = parentAlternate; + } else { + /** + * Make outermost alternate block + * $N = Primitive undefined + * $M = StoreLocal $OptionalResult = $N + * goto fallthrough + */ + alternate = builder.enter('value', () => { + const temp = lowerValueToTemporary(builder, { + kind: 'Primitive', + value: undefined, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...returnPlace}}, + value: {...temp}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + } + + let testIdentifier: Identifier | null = null; + const testBlock = builder.enter('value', () => { + const firstOptional = dep.path.findIndex(path => path.optional); + if (idx === firstOptional) { + // Lower test block + testIdentifier = writeNonOptionalDependency( + { + identifier: dep.identifier, + reactive: dep.reactive, + path: dep.path.slice(0, idx), + }, + env, + builder, + ); + } else { + testIdentifier = writeOptionalDependency( + idx - 1, + dep, + builder, + alternate, + ); + } + + return { + kind: 'branch', + test: { + identifier: testIdentifier, + effect: Effect.Freeze, + kind: 'Identifier', + loc: GeneratedSource, + reactive: dep.reactive, + }, + consequent: consequent.id, + alternate, + id: makeInstructionId(0), + loc: GeneratedSource, + fallthrough: continuationBlock.id, + }; + }); + + builder.enterReserved(consequent, () => { + CompilerError.invariant(testIdentifier !== null, { + reason: 'Satisfy type checker', + description: null, + loc: null, + suggestions: null, + }); + + const tmpConsequent = lowerValueToTemporary(builder, { + kind: 'PropertyLoad', + object: { + identifier: testIdentifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + property: dep.path[idx].property, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...returnPlace}}, + value: {...tmpConsequent}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + builder.terminateWithContinuation( + { + kind: 'optional', + optional: dep.path[idx].optional, + test: testBlock, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }, + continuationBlock, + ); + + return returnPlace.identifier; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index f1a5843419..774773e52f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -29,6 +29,9 @@ import { isSetStateType, isFireFunctionType, makeScopeId, + HIR, + BasicBlock, + BlockId, } from '../HIR'; import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads'; import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies'; @@ -44,7 +47,16 @@ import { DependencyCollectionContext, handleInstruction, } from '../HIR/PropagateScopeDependenciesHIR'; -import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import { + buildDependencyInstructions, + writeNonOptionalDependency, + writeOptionalDependency, +} from '../HIR/ScopeDependencyUtils'; +import { + eachInstructionOperand, + eachTerminalOperand, + terminalFallthrough, +} from '../HIR/visitors'; import {empty} from '../Utils/Stack'; import {getOrInsertWith} from '../Utils/utils'; @@ -53,7 +65,6 @@ import {getOrInsertWith} from '../Utils/utils'; * a second argument to the useEffect call if no dependency array is provided. */ export function inferEffectDependencies(fn: HIRFunction): void { - let hasRewrite = false; const fnExpressions = new Map< IdentifierId, TInstruction @@ -86,6 +97,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) */ const reactiveIds = inferReactiveIdentifiers(fn); + const rewriteBlocks: Array = []; for (const [, block] of fn.body.blocks) { if (block.terminal.kind === 'scope') { @@ -101,7 +113,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { ); } } - const rewriteInstrs = new Map>(); + const rewriteInstrs: Array = []; for (const instr of block.instructions) { const {value, lvalue} = instr; if (value.kind === 'FunctionExpression') { @@ -165,7 +177,6 @@ export function inferEffectDependencies(fn: HIRFunction): void { ) { // We have a useEffect call with no deps array, so we need to infer the deps const effectDeps: Array = []; - const newInstructions: Array = []; const deps: ArrayExpression = { kind: 'ArrayExpression', elements: effectDeps, @@ -196,24 +207,28 @@ export function inferEffectDependencies(fn: HIRFunction): void { */ const usedDeps = []; - for (const dep of minimalDeps) { + for (const maybeDep of minimalDeps) { if ( - ((isUseRefType(dep.identifier) || - isSetStateType(dep.identifier)) && - !reactiveIds.has(dep.identifier.id)) || - isFireFunctionType(dep.identifier) + ((isUseRefType(maybeDep.identifier) || + isSetStateType(maybeDep.identifier)) && + !reactiveIds.has(maybeDep.identifier.id)) || + isFireFunctionType(maybeDep.identifier) ) { // exclude non-reactive hook results, which will never be in a memo block continue; } - const {place, instructions} = writeDependencyToInstructions( + const dep = truncateDepAtCurrent(maybeDep); + const {place, value, exitBlockId} = buildDependencyInstructions( dep, - reactiveIds.has(dep.identifier.id), fn.env, - fnExpr.loc, ); - newInstructions.push(...instructions); + rewriteInstrs.push({ + kind: 'block', + location: instr.id, + value, + exitBlockId: exitBlockId, + }); effectDeps.push(place); usedDeps.push(dep); } @@ -234,27 +249,32 @@ export function inferEffectDependencies(fn: HIRFunction): void { }); } - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, - }); - // Step 2: push the inferred deps array as an argument of the useEffect + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, + }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } else if (loadGlobals.has(value.args[0].identifier.id)) { // Global functions have no reactive dependencies, so we can insert an empty array - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } } else if ( @@ -285,21 +305,13 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } } - if (rewriteInstrs.size > 0) { - hasRewrite = true; - const newInstrs = []; - for (const instr of block.instructions) { - const newInstr = rewriteInstrs.get(instr.id); - if (newInstr != null) { - newInstrs.push(...newInstr, instr); - } else { - newInstrs.push(instr); - } - } - block.instructions = newInstrs; - } + rewriteSplices(block, rewriteInstrs, rewriteBlocks); } - if (hasRewrite) { + + if (rewriteBlocks.length > 0) { + for (const block of rewriteBlocks) { + fn.body.blocks.set(block.id, block); + } // Renumber instructions and fix scope ranges markInstructionIds(fn.body); fixScopeAndIdentifierRanges(fn.body); @@ -307,63 +319,144 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } -function writeDependencyToInstructions( +function truncateDepAtCurrent( dep: ReactiveScopeDependency, - reactive: boolean, - env: Environment, - loc: SourceLocation, -): {place: Place; instructions: Array} { - const instructions: Array = []; - let currValue = createTemporaryPlace(env, GeneratedSource); - currValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...currValue, effect: Effect.Mutate}, - value: { - kind: 'LoadLocal', - place: { - kind: 'Identifier', - identifier: dep.identifier, - effect: Effect.Capture, - reactive, - loc: loc, - }, - loc: loc, - }, - }); - for (const path of dep.path) { - if (path.optional) { - /** - * TODO: instead of truncating optional paths, reuse - * instructions from hoisted dependencies block(s) - */ - break; +): ReactiveScopeDependency { + const idx = dep.path.findIndex(path => path.property === 'current'); + if (idx === -1) { + return dep; + } else { + return {...dep, path: dep.path.slice(0, idx)}; + } +} + +type SpliceInfo = + | {kind: 'instr'; location: InstructionId; value: Instruction} + | { + kind: 'block'; + location: InstructionId; + value: HIR; + exitBlockId: BlockId; + }; + +function rewriteSplices( + originalBlock: BasicBlock, + splices: Array, + rewriteBlocks: Array, +) { + if (splices.length === 0) { + return; + } + /** + * Splice instructions or value blocks into the original block. + * --- original block --- + * bb_original + * instr1 + * ... + * instr2 <-- splice location + * instr3 + * ... + * + * + * If there is more than one block in the splice, this means that we're + * splicing in a set of value-blocks of the following structure: + * --- blocks we're splicing in --- + * bb_entry: + * instrEntry + * ... + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... + * + * + * + * --- rewritten blocks --- + * bb_original + * instr1 + * ... (original instructions) + * instr2 + * instrEntry + * ... (spliced instructions) + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... (spliced instructions) + * instr3 + * ... (original instructions) + * + */ + const originalInstrs = originalBlock.instructions; + let currBlock: BasicBlock = {...originalBlock, instructions: []}; + rewriteBlocks.push(currBlock); + + let cursor = 0; + for (const rewrite of splices) { + while (originalInstrs[cursor].id < rewrite.location) { + CompilerError.invariant( + originalInstrs[cursor].id < originalInstrs[cursor + 1].id, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted', + loc: originalInstrs[cursor].loc, + }, + ); + currBlock.instructions.push(originalInstrs[cursor]); + cursor++; } - if (path.property === 'current') { - /* - * Prune ref.current accesses. This may over-capture for non-ref values with - * a current property, but that's fine. - */ - break; + CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, { + reason: + '[InferEffectDependencies] Internal invariant broken: splice location not found', + loc: originalInstrs[cursor].loc, + }); + + if (rewrite.kind === 'instr') { + currBlock.instructions.push(rewrite.value); + } else { + const {entry, blocks} = rewrite.value; + const entryBlock = blocks.get(entry)!; + // splice in all instructions from the entry block + currBlock.instructions.push(...entryBlock.instructions); + if (blocks.size > 1) { + /** + * We're splicing in a set of value-blocks, which means we need + * to push new blocks and update terminals. + */ + CompilerError.invariant( + terminalFallthrough(entryBlock.terminal) === rewrite.exitBlockId, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough', + loc: entryBlock.terminal.loc, + }, + ); + const originalTerminal = currBlock.terminal; + currBlock.terminal = entryBlock.terminal; + + for (const [id, block] of blocks) { + if (id === entry) { + continue; + } + if (id === rewrite.exitBlockId) { + block.terminal = originalTerminal; + currBlock = block; + } + rewriteBlocks.push(block); + } + } } - const nextValue = createTemporaryPlace(env, GeneratedSource); - nextValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...nextValue, effect: Effect.Mutate}, - value: { - kind: 'PropertyLoad', - object: {...currValue, effect: Effect.Capture}, - property: path.property, - loc: loc, - }, - }); - currValue = nextValue; + } + for (let i = cursor; i < originalInstrs.length; i++) { + currBlock.instructions.push(originalInstrs[i]); } - currValue.effect = Effect.Freeze; - return {place: currValue, instructions}; } function inferReactiveIdentifiers(fn: HIRFunction): Set { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md index 7c9f21b85c..d2414f3f9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md @@ -8,10 +8,16 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + ``` ## Code @@ -23,7 +29,7 @@ import { print } from "shared-runtime"; // TODO: take optional chains as dependencies function ReactiveMemberExpr(t0) { - const $ = _c(7); + const $ = _c(9); const { cond, propVal } = t0; let t1; if ($[0] !== cond || $[1] !== propVal) { @@ -36,7 +42,7 @@ function ReactiveMemberExpr(t0) { } let t2; if ($[3] !== t1) { - t2 = { a: t1 }; + t2 = { a: t1, c: null }; $[3] = t1; $[4] = t2; } else { @@ -51,10 +57,25 @@ function ReactiveMemberExpr(t0) { } else { t3 = $[6]; } - useEffect(t3, [obj.a]); + useEffect(t3, [obj.a?.b]); + let t4; + if ($[7] !== obj.c?.d) { + t4 = () => print(obj.c?.d); + $[7] = obj.c?.d; + $[8] = t4; + } else { + t4 = $[8]; + } + useEffect(t4, [obj.c?.d]); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) +logs: [1,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js index 8a76784e24..4f567fa152 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js @@ -4,6 +4,12 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; From 2083bebceaa8f71e8350b7833af1b651f933dd65 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 14:10:04 -0400 Subject: [PATCH 400/422] [compiler] Add reactive flag on scope dependencies Summary: Test Plan: Summary: Test Plan: --- .../src/HIR/CollectHoistablePropertyLoads.ts | 28 ++++++++++---- .../HIR/CollectOptionalChainDependencies.ts | 2 + .../src/HIR/DeriveMinimalDependenciesHIR.ts | 36 +++++++++++++----- .../src/HIR/HIR.ts | 12 ++++++ .../src/HIR/PropagateScopeDependenciesHIR.ts | 11 +++++- .../src/Inference/InferReactivePlaces.ts | 38 ++++++++++++++++++- ...rgeReactiveScopesThatInvalidateTogether.ts | 1 + 7 files changed, 109 insertions(+), 19 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 ea7268c573..a11822538f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -241,7 +241,10 @@ type PropertyPathNode = class PropertyPathRegistry { roots: Map = new Map(); - getOrCreateIdentifier(identifier: Identifier): PropertyPathNode { + getOrCreateIdentifier( + identifier: Identifier, + reactive: boolean, + ): PropertyPathNode { /** * Reads from a statically scoped variable are always safe in JS, * with the exception of TDZ (not addressed by this pass). @@ -255,12 +258,19 @@ class PropertyPathRegistry { optionalProperties: new Map(), fullPath: { identifier, + reactive, path: [], }, hasOptional: false, parent: null, }; this.roots.set(identifier.id, rootNode); + } else { + CompilerError.invariant(reactive === rootNode.fullPath.reactive, { + reason: + '[HoistablePropertyLoads] Found inconsistencies in `reactive` flag when deduping identifier reads within the same scope', + loc: identifier.loc, + }); } return rootNode; } @@ -278,6 +288,7 @@ class PropertyPathRegistry { parent: parent, fullPath: { identifier: parent.fullPath.identifier, + reactive: parent.fullPath.reactive, path: parent.fullPath.path.concat(entry), }, hasOptional: parent.hasOptional || entry.optional, @@ -293,7 +304,7 @@ class PropertyPathRegistry { * so all subpaths of a PropertyLoad should already exist * (e.g. a.b is added before a.b.c), */ - let currNode = this.getOrCreateIdentifier(n.identifier); + let currNode = this.getOrCreateIdentifier(n.identifier, n.reactive); if (n.path.length === 0) { return currNode; } @@ -315,10 +326,11 @@ function getMaybeNonNullInInstruction( instr: InstructionValue, context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { - let path = null; + let path: ReactiveScopeDependency | null = null; if (instr.kind === 'PropertyLoad') { path = context.temporaries.get(instr.object.identifier.id) ?? { identifier: instr.object.identifier, + reactive: instr.object.reactive, path: [], }; } else if (instr.kind === 'Destructure') { @@ -381,7 +393,7 @@ function collectNonNullsInBlocks( ) { const identifier = fn.params[0].identifier; knownNonNullIdentifiers.add( - context.registry.getOrCreateIdentifier(identifier), + context.registry.getOrCreateIdentifier(identifier, true), ); } const nodes = new Map< @@ -616,9 +628,11 @@ function reduceMaybeOptionalChains( changed = false; for (const original of optionalChainNodes) { - let {identifier, path: origPath} = original.fullPath; - let currNode: PropertyPathNode = - registry.getOrCreateIdentifier(identifier); + let {identifier, path: origPath, reactive} = original.fullPath; + let currNode: PropertyPathNode = registry.getOrCreateIdentifier( + identifier, + reactive, + ); for (let i = 0; i < origPath.length; i++) { const entry = origPath[i]; // If the base is known to be non-null, replace with a non-optional load 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 cb787d04d0..75dad4c1bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -290,6 +290,7 @@ function traverseOptionalBlock( ); baseObject = { identifier: maybeTest.instructions[0].value.place.identifier, + reactive: maybeTest.instructions[0].value.place.reactive, path, }; test = maybeTest.terminal; @@ -391,6 +392,7 @@ function traverseOptionalBlock( ); const load = { identifier: baseObject.identifier, + reactive: baseObject.reactive, path: [ ...baseObject.path, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts index 7f6fb9e88f..7819ab39b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts @@ -25,8 +25,9 @@ export class ReactiveScopeDependencyTreeHIR { * `identifier.path`, or `identifier?.path` is in this map, it is safe to * evaluate (non-optional) PropertyLoads from. */ - #hoistableObjects: Map = new Map(); - #deps: Map = new Map(); + #hoistableObjects: Map = + new Map(); + #deps: Map = new Map(); /** * @param hoistableObjects a set of paths from which we can safely evaluate @@ -35,9 +36,10 @@ export class ReactiveScopeDependencyTreeHIR { * duplicates when traversing the CFG. */ constructor(hoistableObjects: Iterable) { - for (const {path, identifier} of hoistableObjects) { + for (const {path, identifier, reactive} of hoistableObjects) { let currNode = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot( identifier, + reactive, this.#hoistableObjects, path.length > 0 && path[0].optional ? 'Optional' : 'NonNull', ); @@ -70,7 +72,8 @@ export class ReactiveScopeDependencyTreeHIR { static #getOrCreateRoot( identifier: Identifier, - roots: Map>, + reactive: boolean, + roots: Map & {reactive: boolean}>, defaultAccessType: T, ): TreeNode { // roots can always be accessed unconditionally in JS @@ -79,9 +82,16 @@ export class ReactiveScopeDependencyTreeHIR { if (rootNode === undefined) { rootNode = { properties: new Map(), + reactive, accessType: defaultAccessType, }; roots.set(identifier, rootNode); + } else { + CompilerError.invariant(reactive === rootNode.reactive, { + reason: '[DeriveMinimalDependenciesHIR] Conflicting reactive root flag', + description: `Identifier ${printIdentifier(identifier)}`, + loc: GeneratedSource, + }); } return rootNode; } @@ -92,9 +102,10 @@ export class ReactiveScopeDependencyTreeHIR { * safe-to-evaluate subpath */ addDependency(dep: ReactiveScopeDependency): void { - const {identifier, path} = dep; + const {identifier, reactive, path} = dep; let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot( identifier, + reactive, this.#deps, PropertyAccessType.UnconditionalAccess, ); @@ -172,7 +183,13 @@ export class ReactiveScopeDependencyTreeHIR { deriveMinimalDependencies(): Set { const results = new Set(); for (const [rootId, rootNode] of this.#deps.entries()) { - collectMinimalDependenciesInSubtree(rootNode, rootId, [], results); + collectMinimalDependenciesInSubtree( + rootNode, + rootNode.reactive, + rootId, + [], + results, + ); } return results; @@ -294,25 +311,24 @@ type HoistableNode = TreeNode<'Optional' | 'NonNull'>; type DependencyNode = TreeNode; /** - * TODO: this is directly pasted from DeriveMinimalDependencies. Since we no - * longer have conditionally accessed nodes, we can simplify - * * Recursively calculates minimal dependencies in a subtree. * @param node DependencyNode representing a dependency subtree. * @returns a minimal list of dependencies in this subtree. */ function collectMinimalDependenciesInSubtree( node: DependencyNode, + reactive: boolean, rootIdentifier: Identifier, path: Array, results: Set, ): void { if (isDependency(node.accessType)) { - results.add({identifier: rootIdentifier, path}); + results.add({identifier: rootIdentifier, reactive, path}); } else { for (const [childName, childNode] of node.properties) { collectMinimalDependenciesInSubtree( childNode, + reactive, rootIdentifier, [ ...path, 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 99b8c189ee..1699a0fc3d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1568,6 +1568,18 @@ export type DependencyPathEntry = { export type DependencyPath = Array; export type ReactiveScopeDependency = { identifier: Identifier; + /** + * Reflects whether the base identifier is reactive. Note that some reactive + * objects may have non-reactive properties, but we do not currently track + * this. + * + * ```js + * // Technically, result[0] is reactive and result[1] is not. + * // Currently, both dependencies would be marked as reactive. + * const result = useState(); + * ``` + */ + reactive: boolean; path: DependencyPath; }; 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 96b9e51710..91b7712b88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -316,6 +316,7 @@ function collectTemporariesSidemapImpl( ) { temporaries.set(lvalue.identifier.id, { identifier: value.place.identifier, + reactive: value.place.reactive, path: [], }); } @@ -369,11 +370,13 @@ function getProperty( if (resolvedDependency == null) { property = { identifier: object.identifier, + reactive: object.reactive, path: [{property: propertyName, optional}], }; } else { property = { identifier: resolvedDependency.identifier, + reactive: resolvedDependency.reactive, path: [...resolvedDependency.path, {property: propertyName, optional}], }; } @@ -532,6 +535,7 @@ export class DependencyCollectionContext { this.visitDependency( this.#temporaries.get(place.identifier.id) ?? { identifier: place.identifier, + reactive: place.reactive, path: [], }, ); @@ -596,6 +600,7 @@ export class DependencyCollectionContext { ) { maybeDependency = { identifier: maybeDependency.identifier, + reactive: maybeDependency.reactive, path: [], }; } @@ -617,7 +622,11 @@ export class DependencyCollectionContext { identifier => identifier.declarationId === place.identifier.declarationId, ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) + this.#checkValidDependency({ + identifier: place.identifier, + reactive: place.reactive, + path: [], + }) ) { currentScope.reassignments.add(place.identifier); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts index b05b292124..88faccd8cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts @@ -26,6 +26,7 @@ import { import {PostDominator} from '../HIR/Dominator'; import { eachInstructionLValue, + eachInstructionOperand, eachInstructionValueOperand, eachTerminalOperand, } from '../HIR/visitors'; @@ -292,7 +293,7 @@ export function inferReactivePlaces(fn: HIRFunction): void { let hasReactiveInput = false; /* * NOTE: we want to mark all operands as reactive or not, so we - * avoid short-circuting here + * avoid short-circuiting here */ for (const operand of eachInstructionValueOperand(value)) { const reactive = reactiveIdentifiers.isReactive(operand); @@ -375,6 +376,41 @@ export function inferReactivePlaces(fn: HIRFunction): void { } } } while (reactiveIdentifiers.snapshot()); + + function propagateReactivityToInnerFunctions( + fn: HIRFunction, + isOutermost: boolean, + ): void { + for (const [, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if (!isOutermost) { + for (const operand of eachInstructionOperand(instr)) { + reactiveIdentifiers.isReactive(operand); + } + } + if ( + instr.value.kind === 'ObjectMethod' || + instr.value.kind === 'FunctionExpression' + ) { + propagateReactivityToInnerFunctions( + instr.value.loweredFunc.func, + false, + ); + } + } + if (!isOutermost) { + for (const operand of eachTerminalOperand(block.terminal)) { + reactiveIdentifiers.isReactive(operand); + } + } + } + } + + /** + * Propagate reactivity for inner functions, as we eventually hoist and dedupe + * dependency instructions for scopes. + */ + propagateReactivityToInnerFunctions(fn, true); } /* diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts index 08d2212d86..6f2d97ff8e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts @@ -456,6 +456,7 @@ function canMergeScopes( new Set( [...current.scope.declarations.values()].map(declaration => ({ identifier: declaration.identifier, + reactive: true, path: [], })), ), From 7c6189779d0100084a5f39d876505723f790322b Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 14:10:04 -0400 Subject: [PATCH 401/422] [compiler] Prepare HIRBuilder to be used by later passes --- .../src/Entrypoint/Pipeline.ts | 1 + .../src/HIR/BuildHIR.ts | 25 ++++++++--------- .../src/HIR/Environment.ts | 5 +++- .../src/HIR/HIRBuilder.ts | 28 +++++++++++-------- 4 files changed, 34 insertions(+), 25 deletions(-) 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 831d1ca380..fe97c8d642 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -130,6 +130,7 @@ function run( mode, config, contextIdentifiers, + func, logger, filename, code, 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 b9f82eea18..cfb15fb595 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -70,12 +70,14 @@ import {BuiltInArrayId} from './ObjectShape'; export function lower( func: NodePath, env: Environment, + // Bindings captured from the outer function, in case lower() is called recursively (for lambdas) bindings: Bindings | null = null, capturedRefs: Array = [], - // the outermost function being compiled, in case lower() is called recursively (for lambdas) - parent: NodePath | null = null, ): Result { - const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs); + const builder = new HIRBuilder(env, { + bindings, + context: capturedRefs, + }); const context: HIRFunction['context'] = []; for (const ref of capturedRefs ?? []) { @@ -215,7 +217,7 @@ export function lower( return Ok({ id, params, - fnType: parent == null ? env.fnType : 'Other', + fnType: bindings == null ? env.fnType : 'Other', returnTypeAnnotation: null, // TODO: extract the actual return type node if present returnType: makeType(), body: builder.build(), @@ -3417,7 +3419,7 @@ function lowerFunction( | t.ObjectMethod >, ): LoweredFunction | null { - const componentScope: Scope = builder.parentFunction.scope; + const componentScope: Scope = builder.environment.parentFunction.scope; const capturedContext = gatherCapturedContext(expr, componentScope); /* @@ -3428,13 +3430,10 @@ function lowerFunction( * This isn't a problem in practice because use Babel's scope analysis to * identify the correct references. */ - const lowering = lower( - expr, - builder.environment, - builder.bindings, - [...builder.context, ...capturedContext], - builder.parentFunction, - ); + const lowering = lower(expr, builder.environment, builder.bindings, [ + ...builder.context, + ...capturedContext, + ]); let loweredFunc: HIRFunction; if (lowering.isErr()) { lowering @@ -3456,7 +3455,7 @@ function lowerExpressionToTemporary( return lowerValueToTemporary(builder, value); } -function lowerValueToTemporary( +export function lowerValueToTemporary( builder: HIRBuilder, value: InstructionValue, ): Place { 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 6e6643cd1d..27b578b3c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -47,7 +47,7 @@ import { ShapeRegistry, addHook, } from './ObjectShape'; -import {Scope as BabelScope} from '@babel/traverse'; +import {Scope as BabelScope, NodePath} from '@babel/traverse'; import {TypeSchema} from './TypeSchema'; export const ReactElementSymbolSchema = z.object({ @@ -675,6 +675,7 @@ export class Environment { #contextIdentifiers: Set; #hoistedIdentifiers: Set; + parentFunction: NodePath; constructor( scope: BabelScope, @@ -682,6 +683,7 @@ export class Environment { compilerMode: CompilerMode, config: EnvironmentConfig, contextIdentifiers: Set, + parentFunction: NodePath, // the outermost function being compiled logger: Logger | null, filename: string | null, code: string | null, @@ -740,6 +742,7 @@ export class Environment { this.#moduleTypes.set(REANIMATED_MODULE_NAME, reanimatedModuleType); } + this.parentFunction = parentFunction; this.#contextIdentifiers = contextIdentifiers; this.#hoistedIdentifiers = new Set(); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 44dd34b7d6..9ed37bb2fc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -110,7 +110,6 @@ export default class HIRBuilder { #bindings: Bindings; #env: Environment; #exceptionHandlerStack: Array = []; - parentFunction: NodePath; errors: CompilerError = new CompilerError(); /** * Traversal context: counts the number of `fbt` tag parents @@ -136,16 +135,17 @@ export default class HIRBuilder { constructor( env: Environment, - parentFunction: NodePath, // the outermost function being compiled - bindings: Bindings | null = null, - context: Array | null = null, + options?: { + bindings?: Bindings | null; + context?: Array; + entryBlockKind?: BlockKind; + }, ) { this.#env = env; - this.#bindings = bindings ?? new Map(); - this.parentFunction = parentFunction; - this.#context = context ?? []; + this.#bindings = options?.bindings ?? new Map(); + this.#context = options?.context ?? []; this.#entry = makeBlockId(env.nextBlockId); - this.#current = newBlock(this.#entry, 'block'); + this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block'); } currentBlockKind(): BlockKind { @@ -239,7 +239,7 @@ export default class HIRBuilder { // Check if the binding is from module scope const outerBinding = - this.parentFunction.scope.parent.getBinding(originalName); + this.#env.parentFunction.scope.parent.getBinding(originalName); if (babelBinding === outerBinding) { const path = babelBinding.path; if (path.isImportDefaultSpecifier()) { @@ -293,7 +293,7 @@ export default class HIRBuilder { const binding = this.#resolveBabelBinding(path); if (binding) { // Check if the binding is from module scope, if so return null - const outerBinding = this.parentFunction.scope.parent.getBinding( + const outerBinding = this.#env.parentFunction.scope.parent.getBinding( path.node.name, ); if (binding === outerBinding) { @@ -376,7 +376,7 @@ export default class HIRBuilder { } // Terminate the current block w the given terminal, and start a new block - terminate(terminal: Terminal, nextBlockKind: BlockKind | null): void { + terminate(terminal: Terminal, nextBlockKind: BlockKind | null): BlockId { const {id: blockId, kind, instructions} = this.#current; this.#completed.set(blockId, { kind, @@ -390,6 +390,7 @@ export default class HIRBuilder { const nextId = this.#env.nextBlockId; this.#current = newBlock(nextId, nextBlockKind); } + return blockId; } /* @@ -746,6 +747,11 @@ function getReversePostorderedBlocks(func: HIR): HIR['blocks'] { * (eg bb2 then bb1), we ensure that they get reversed back to the correct order. */ const block = func.blocks.get(blockId)!; + CompilerError.invariant(block != null, { + reason: '[HIRBuilder] Unexpected null block', + description: `expected block ${blockId} to exist`, + loc: GeneratedSource, + }); const successors = [...eachTerminalSuccessor(block.terminal)].reverse(); const fallthrough = terminalFallthrough(block.terminal); From 84347ecff2ad2d81714829e65ffbfd4bc73e097f Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 14:11:54 -0400 Subject: [PATCH 402/422] [compiler] Inferred effect dependencies now include optional chains Inferred effect dependencies now include optional chains. This is a temporary solution while https://github.com/facebook/react/pull/32099 and its followups are worked on. Ideally, we should model reactive scope dependencies in the IR similarly to `ComputeIR` -- dependencies should be hoisted and all references rewritten to use the hoisted dependencies. (will add more test cases) --- .../src/HIR/ScopeDependencyUtils.ts | 280 ++++++++++++++++++ .../src/Inference/InferEffectDependencies.ts | 278 +++++++++++------ .../reactive-optional-chain.expect.md | 31 +- .../reactive-optional-chain.js | 8 +- 4 files changed, 496 insertions(+), 101 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts new file mode 100644 index 0000000000..b986cd211b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts @@ -0,0 +1,280 @@ +import { + Place, + ReactiveScopeDependency, + Identifier, + makeInstructionId, + InstructionKind, + GeneratedSource, + BlockId, + makeTemporaryIdentifier, + Effect, + GotoVariant, + HIR, +} from './HIR'; +import {CompilerError} from '../CompilerError'; +import {Environment} from './Environment'; +import HIRBuilder from './HIRBuilder'; +import {lowerValueToTemporary} from './BuildHIR'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; + +type DependencyInstructions = { + place: Place; + value: HIR; + exitBlockId: BlockId; +}; + +export function buildDependencyInstructions( + dep: ReactiveScopeDependency, + env: Environment, +): DependencyInstructions { + const builder = new HIRBuilder(env, { + entryBlockKind: 'value', + }); + let result: Place; + if (dep.path.every(path => !path.optional)) { + const last = writeNonOptionalDependency(dep, env, builder); + result = { + kind: 'Identifier', + identifier: last, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }; + } else { + const last = writeOptionalDependency( + dep.path.length - 1, + dep, + builder, + null, + ); + result = { + kind: 'Identifier', + identifier: last, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }; + } + + const exitBlockId = builder.terminate( + { + kind: 'unsupported', + loc: GeneratedSource, + id: makeInstructionId(0), + }, + null, + ); + return { + place: result, + value: builder.build(), + exitBlockId, + }; +} +function writeNonOptionalDependency( + dep: ReactiveScopeDependency, + env: Environment, + builder: HIRBuilder, +): Identifier { + const loc = dep.identifier.loc; + let last: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: last, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'LoadLocal', + place: { + identifier: dep.identifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + + for (const path of dep.path) { + const next = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: next, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'PropertyLoad', + object: { + identifier: last, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + property: path.property, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + last = next; + } + return last; +} + +function writeOptionalDependency( + idx: number, + dep: ReactiveScopeDependency, + builder: HIRBuilder, + parentAlternate: BlockId | null, +): Identifier { + const env = builder.environment; + CompilerError.invariant( + idx >= 0 && !dep.path.slice(0, idx + 1).every(path => !path.optional), + { + reason: '[WriteOptional] Expected optional path', + description: `${idx} ${printDependency(dep)}`, + loc: GeneratedSource, + }, + ); + const continuationBlock = builder.reserve(builder.currentBlockKind()); + const consequent = builder.reserve('value'); + + const returnPlace: Place = { + kind: 'Identifier', + identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource), + effect: Effect.Mutate, + reactive: dep.reactive, + loc: GeneratedSource, + }; + + let alternate; + if (parentAlternate != null) { + alternate = parentAlternate; + } else { + /** + * Make outermost alternate block + * $N = Primitive undefined + * $M = StoreLocal $OptionalResult = $N + * goto fallthrough + */ + alternate = builder.enter('value', () => { + const temp = lowerValueToTemporary(builder, { + kind: 'Primitive', + value: undefined, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...returnPlace}}, + value: {...temp}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + } + + let testIdentifier: Identifier | null = null; + const testBlock = builder.enter('value', () => { + const firstOptional = dep.path.findIndex(path => path.optional); + if (idx === firstOptional) { + // Lower test block + testIdentifier = writeNonOptionalDependency( + { + identifier: dep.identifier, + reactive: dep.reactive, + path: dep.path.slice(0, idx), + }, + env, + builder, + ); + } else { + testIdentifier = writeOptionalDependency( + idx - 1, + dep, + builder, + alternate, + ); + } + + return { + kind: 'branch', + test: { + identifier: testIdentifier, + effect: Effect.Freeze, + kind: 'Identifier', + loc: GeneratedSource, + reactive: dep.reactive, + }, + consequent: consequent.id, + alternate, + id: makeInstructionId(0), + loc: GeneratedSource, + fallthrough: continuationBlock.id, + }; + }); + + builder.enterReserved(consequent, () => { + CompilerError.invariant(testIdentifier !== null, { + reason: 'Satisfy type checker', + description: null, + loc: null, + suggestions: null, + }); + + const tmpConsequent = lowerValueToTemporary(builder, { + kind: 'PropertyLoad', + object: { + identifier: testIdentifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + property: dep.path[idx].property, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...returnPlace}}, + value: {...tmpConsequent}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + builder.terminateWithContinuation( + { + kind: 'optional', + optional: dep.path[idx].optional, + test: testBlock, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }, + continuationBlock, + ); + + return returnPlace.identifier; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index f1a5843419..722f9f7587 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -10,7 +10,6 @@ import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, Effect, - Environment, FunctionExpression, GeneratedSource, HIRFunction, @@ -29,6 +28,9 @@ import { isSetStateType, isFireFunctionType, makeScopeId, + HIR, + BasicBlock, + BlockId, } from '../HIR'; import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads'; import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies'; @@ -44,7 +46,12 @@ import { DependencyCollectionContext, handleInstruction, } from '../HIR/PropagateScopeDependenciesHIR'; -import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {buildDependencyInstructions} from '../HIR/ScopeDependencyUtils'; +import { + eachInstructionOperand, + eachTerminalOperand, + terminalFallthrough, +} from '../HIR/visitors'; import {empty} from '../Utils/Stack'; import {getOrInsertWith} from '../Utils/utils'; @@ -53,7 +60,6 @@ import {getOrInsertWith} from '../Utils/utils'; * a second argument to the useEffect call if no dependency array is provided. */ export function inferEffectDependencies(fn: HIRFunction): void { - let hasRewrite = false; const fnExpressions = new Map< IdentifierId, TInstruction @@ -86,6 +92,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) */ const reactiveIds = inferReactiveIdentifiers(fn); + const rewriteBlocks: Array = []; for (const [, block] of fn.body.blocks) { if (block.terminal.kind === 'scope') { @@ -101,7 +108,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { ); } } - const rewriteInstrs = new Map>(); + const rewriteInstrs: Array = []; for (const instr of block.instructions) { const {value, lvalue} = instr; if (value.kind === 'FunctionExpression') { @@ -165,7 +172,6 @@ export function inferEffectDependencies(fn: HIRFunction): void { ) { // We have a useEffect call with no deps array, so we need to infer the deps const effectDeps: Array = []; - const newInstructions: Array = []; const deps: ArrayExpression = { kind: 'ArrayExpression', elements: effectDeps, @@ -196,24 +202,28 @@ export function inferEffectDependencies(fn: HIRFunction): void { */ const usedDeps = []; - for (const dep of minimalDeps) { + for (const maybeDep of minimalDeps) { if ( - ((isUseRefType(dep.identifier) || - isSetStateType(dep.identifier)) && - !reactiveIds.has(dep.identifier.id)) || - isFireFunctionType(dep.identifier) + ((isUseRefType(maybeDep.identifier) || + isSetStateType(maybeDep.identifier)) && + !reactiveIds.has(maybeDep.identifier.id)) || + isFireFunctionType(maybeDep.identifier) ) { // exclude non-reactive hook results, which will never be in a memo block continue; } - const {place, instructions} = writeDependencyToInstructions( + const dep = truncateDepAtCurrent(maybeDep); + const {place, value, exitBlockId} = buildDependencyInstructions( dep, - reactiveIds.has(dep.identifier.id), fn.env, - fnExpr.loc, ); - newInstructions.push(...instructions); + rewriteInstrs.push({ + kind: 'block', + location: instr.id, + value, + exitBlockId: exitBlockId, + }); effectDeps.push(place); usedDeps.push(dep); } @@ -234,27 +244,32 @@ export function inferEffectDependencies(fn: HIRFunction): void { }); } - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, - }); - // Step 2: push the inferred deps array as an argument of the useEffect + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, + }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } else if (loadGlobals.has(value.args[0].identifier.id)) { // Global functions have no reactive dependencies, so we can insert an empty array - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } } else if ( @@ -285,21 +300,13 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } } - if (rewriteInstrs.size > 0) { - hasRewrite = true; - const newInstrs = []; - for (const instr of block.instructions) { - const newInstr = rewriteInstrs.get(instr.id); - if (newInstr != null) { - newInstrs.push(...newInstr, instr); - } else { - newInstrs.push(instr); - } - } - block.instructions = newInstrs; - } + rewriteSplices(block, rewriteInstrs, rewriteBlocks); } - if (hasRewrite) { + + if (rewriteBlocks.length > 0) { + for (const block of rewriteBlocks) { + fn.body.blocks.set(block.id, block); + } // Renumber instructions and fix scope ranges markInstructionIds(fn.body); fixScopeAndIdentifierRanges(fn.body); @@ -307,63 +314,144 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } -function writeDependencyToInstructions( +function truncateDepAtCurrent( dep: ReactiveScopeDependency, - reactive: boolean, - env: Environment, - loc: SourceLocation, -): {place: Place; instructions: Array} { - const instructions: Array = []; - let currValue = createTemporaryPlace(env, GeneratedSource); - currValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...currValue, effect: Effect.Mutate}, - value: { - kind: 'LoadLocal', - place: { - kind: 'Identifier', - identifier: dep.identifier, - effect: Effect.Capture, - reactive, - loc: loc, - }, - loc: loc, - }, - }); - for (const path of dep.path) { - if (path.optional) { - /** - * TODO: instead of truncating optional paths, reuse - * instructions from hoisted dependencies block(s) - */ - break; +): ReactiveScopeDependency { + const idx = dep.path.findIndex(path => path.property === 'current'); + if (idx === -1) { + return dep; + } else { + return {...dep, path: dep.path.slice(0, idx)}; + } +} + +type SpliceInfo = + | {kind: 'instr'; location: InstructionId; value: Instruction} + | { + kind: 'block'; + location: InstructionId; + value: HIR; + exitBlockId: BlockId; + }; + +function rewriteSplices( + originalBlock: BasicBlock, + splices: Array, + rewriteBlocks: Array, +): void { + if (splices.length === 0) { + return; + } + /** + * Splice instructions or value blocks into the original block. + * --- original block --- + * bb_original + * instr1 + * ... + * instr2 <-- splice location + * instr3 + * ... + * + * + * If there is more than one block in the splice, this means that we're + * splicing in a set of value-blocks of the following structure: + * --- blocks we're splicing in --- + * bb_entry: + * instrEntry + * ... + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... + * + * + * + * --- rewritten blocks --- + * bb_original + * instr1 + * ... (original instructions) + * instr2 + * instrEntry + * ... (spliced instructions) + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... (spliced instructions) + * instr3 + * ... (original instructions) + * + */ + const originalInstrs = originalBlock.instructions; + let currBlock: BasicBlock = {...originalBlock, instructions: []}; + rewriteBlocks.push(currBlock); + + let cursor = 0; + for (const rewrite of splices) { + while (originalInstrs[cursor].id < rewrite.location) { + CompilerError.invariant( + originalInstrs[cursor].id < originalInstrs[cursor + 1].id, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted', + loc: originalInstrs[cursor].loc, + }, + ); + currBlock.instructions.push(originalInstrs[cursor]); + cursor++; } - if (path.property === 'current') { - /* - * Prune ref.current accesses. This may over-capture for non-ref values with - * a current property, but that's fine. - */ - break; + CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, { + reason: + '[InferEffectDependencies] Internal invariant broken: splice location not found', + loc: originalInstrs[cursor].loc, + }); + + if (rewrite.kind === 'instr') { + currBlock.instructions.push(rewrite.value); + } else { + const {entry, blocks} = rewrite.value; + const entryBlock = blocks.get(entry)!; + // splice in all instructions from the entry block + currBlock.instructions.push(...entryBlock.instructions); + if (blocks.size > 1) { + /** + * We're splicing in a set of value-blocks, which means we need + * to push new blocks and update terminals. + */ + CompilerError.invariant( + terminalFallthrough(entryBlock.terminal) === rewrite.exitBlockId, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough', + loc: entryBlock.terminal.loc, + }, + ); + const originalTerminal = currBlock.terminal; + currBlock.terminal = entryBlock.terminal; + + for (const [id, block] of blocks) { + if (id === entry) { + continue; + } + if (id === rewrite.exitBlockId) { + block.terminal = originalTerminal; + currBlock = block; + } + rewriteBlocks.push(block); + } + } } - const nextValue = createTemporaryPlace(env, GeneratedSource); - nextValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...nextValue, effect: Effect.Mutate}, - value: { - kind: 'PropertyLoad', - object: {...currValue, effect: Effect.Capture}, - property: path.property, - loc: loc, - }, - }); - currValue = nextValue; + } + for (let i = cursor; i < originalInstrs.length; i++) { + currBlock.instructions.push(originalInstrs[i]); } - currValue.effect = Effect.Freeze; - return {place: currValue, instructions}; } function inferReactiveIdentifiers(fn: HIRFunction): Set { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md index 7c9f21b85c..d2414f3f9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md @@ -8,10 +8,16 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + ``` ## Code @@ -23,7 +29,7 @@ import { print } from "shared-runtime"; // TODO: take optional chains as dependencies function ReactiveMemberExpr(t0) { - const $ = _c(7); + const $ = _c(9); const { cond, propVal } = t0; let t1; if ($[0] !== cond || $[1] !== propVal) { @@ -36,7 +42,7 @@ function ReactiveMemberExpr(t0) { } let t2; if ($[3] !== t1) { - t2 = { a: t1 }; + t2 = { a: t1, c: null }; $[3] = t1; $[4] = t2; } else { @@ -51,10 +57,25 @@ function ReactiveMemberExpr(t0) { } else { t3 = $[6]; } - useEffect(t3, [obj.a]); + useEffect(t3, [obj.a?.b]); + let t4; + if ($[7] !== obj.c?.d) { + t4 = () => print(obj.c?.d); + $[7] = obj.c?.d; + $[8] = t4; + } else { + t4 = $[8]; + } + useEffect(t4, [obj.c?.d]); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) +logs: [1,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js index 8a76784e24..4f567fa152 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js @@ -4,6 +4,12 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; From 50a37693f1c463ec1234414ffffe747690323321 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 14:10:04 -0400 Subject: [PATCH 403/422] [compiler] Add reactive flag on scope dependencies When collecting scope dependencies, mark each dependency with `reactive: true | false`. This prepares for later PRs https://github.com/facebook/react/pull/33326 and https://github.com/facebook/react/pull/32099 which rewrite scope dependencies into instructions. Note that some reactive objects may have non-reactive properties, but we do not currently track this. Technically, state[0] is reactive and state[1] is not. Currently, both would be marked as reactive. ```js const state = useState(); ``` --- .../src/HIR/CollectHoistablePropertyLoads.ts | 28 ++++++++++---- .../HIR/CollectOptionalChainDependencies.ts | 2 + .../src/HIR/DeriveMinimalDependenciesHIR.ts | 36 +++++++++++++----- .../src/HIR/HIR.ts | 12 ++++++ .../src/HIR/PropagateScopeDependenciesHIR.ts | 11 +++++- .../src/Inference/InferReactivePlaces.ts | 38 ++++++++++++++++++- ...rgeReactiveScopesThatInvalidateTogether.ts | 1 + 7 files changed, 109 insertions(+), 19 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 ea7268c573..a11822538f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -241,7 +241,10 @@ type PropertyPathNode = class PropertyPathRegistry { roots: Map = new Map(); - getOrCreateIdentifier(identifier: Identifier): PropertyPathNode { + getOrCreateIdentifier( + identifier: Identifier, + reactive: boolean, + ): PropertyPathNode { /** * Reads from a statically scoped variable are always safe in JS, * with the exception of TDZ (not addressed by this pass). @@ -255,12 +258,19 @@ class PropertyPathRegistry { optionalProperties: new Map(), fullPath: { identifier, + reactive, path: [], }, hasOptional: false, parent: null, }; this.roots.set(identifier.id, rootNode); + } else { + CompilerError.invariant(reactive === rootNode.fullPath.reactive, { + reason: + '[HoistablePropertyLoads] Found inconsistencies in `reactive` flag when deduping identifier reads within the same scope', + loc: identifier.loc, + }); } return rootNode; } @@ -278,6 +288,7 @@ class PropertyPathRegistry { parent: parent, fullPath: { identifier: parent.fullPath.identifier, + reactive: parent.fullPath.reactive, path: parent.fullPath.path.concat(entry), }, hasOptional: parent.hasOptional || entry.optional, @@ -293,7 +304,7 @@ class PropertyPathRegistry { * so all subpaths of a PropertyLoad should already exist * (e.g. a.b is added before a.b.c), */ - let currNode = this.getOrCreateIdentifier(n.identifier); + let currNode = this.getOrCreateIdentifier(n.identifier, n.reactive); if (n.path.length === 0) { return currNode; } @@ -315,10 +326,11 @@ function getMaybeNonNullInInstruction( instr: InstructionValue, context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { - let path = null; + let path: ReactiveScopeDependency | null = null; if (instr.kind === 'PropertyLoad') { path = context.temporaries.get(instr.object.identifier.id) ?? { identifier: instr.object.identifier, + reactive: instr.object.reactive, path: [], }; } else if (instr.kind === 'Destructure') { @@ -381,7 +393,7 @@ function collectNonNullsInBlocks( ) { const identifier = fn.params[0].identifier; knownNonNullIdentifiers.add( - context.registry.getOrCreateIdentifier(identifier), + context.registry.getOrCreateIdentifier(identifier, true), ); } const nodes = new Map< @@ -616,9 +628,11 @@ function reduceMaybeOptionalChains( changed = false; for (const original of optionalChainNodes) { - let {identifier, path: origPath} = original.fullPath; - let currNode: PropertyPathNode = - registry.getOrCreateIdentifier(identifier); + let {identifier, path: origPath, reactive} = original.fullPath; + let currNode: PropertyPathNode = registry.getOrCreateIdentifier( + identifier, + reactive, + ); for (let i = 0; i < origPath.length; i++) { const entry = origPath[i]; // If the base is known to be non-null, replace with a non-optional load 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 cb787d04d0..75dad4c1bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -290,6 +290,7 @@ function traverseOptionalBlock( ); baseObject = { identifier: maybeTest.instructions[0].value.place.identifier, + reactive: maybeTest.instructions[0].value.place.reactive, path, }; test = maybeTest.terminal; @@ -391,6 +392,7 @@ function traverseOptionalBlock( ); const load = { identifier: baseObject.identifier, + reactive: baseObject.reactive, path: [ ...baseObject.path, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts index 7f6fb9e88f..7819ab39b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts @@ -25,8 +25,9 @@ export class ReactiveScopeDependencyTreeHIR { * `identifier.path`, or `identifier?.path` is in this map, it is safe to * evaluate (non-optional) PropertyLoads from. */ - #hoistableObjects: Map = new Map(); - #deps: Map = new Map(); + #hoistableObjects: Map = + new Map(); + #deps: Map = new Map(); /** * @param hoistableObjects a set of paths from which we can safely evaluate @@ -35,9 +36,10 @@ export class ReactiveScopeDependencyTreeHIR { * duplicates when traversing the CFG. */ constructor(hoistableObjects: Iterable) { - for (const {path, identifier} of hoistableObjects) { + for (const {path, identifier, reactive} of hoistableObjects) { let currNode = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot( identifier, + reactive, this.#hoistableObjects, path.length > 0 && path[0].optional ? 'Optional' : 'NonNull', ); @@ -70,7 +72,8 @@ export class ReactiveScopeDependencyTreeHIR { static #getOrCreateRoot( identifier: Identifier, - roots: Map>, + reactive: boolean, + roots: Map & {reactive: boolean}>, defaultAccessType: T, ): TreeNode { // roots can always be accessed unconditionally in JS @@ -79,9 +82,16 @@ export class ReactiveScopeDependencyTreeHIR { if (rootNode === undefined) { rootNode = { properties: new Map(), + reactive, accessType: defaultAccessType, }; roots.set(identifier, rootNode); + } else { + CompilerError.invariant(reactive === rootNode.reactive, { + reason: '[DeriveMinimalDependenciesHIR] Conflicting reactive root flag', + description: `Identifier ${printIdentifier(identifier)}`, + loc: GeneratedSource, + }); } return rootNode; } @@ -92,9 +102,10 @@ export class ReactiveScopeDependencyTreeHIR { * safe-to-evaluate subpath */ addDependency(dep: ReactiveScopeDependency): void { - const {identifier, path} = dep; + const {identifier, reactive, path} = dep; let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot( identifier, + reactive, this.#deps, PropertyAccessType.UnconditionalAccess, ); @@ -172,7 +183,13 @@ export class ReactiveScopeDependencyTreeHIR { deriveMinimalDependencies(): Set { const results = new Set(); for (const [rootId, rootNode] of this.#deps.entries()) { - collectMinimalDependenciesInSubtree(rootNode, rootId, [], results); + collectMinimalDependenciesInSubtree( + rootNode, + rootNode.reactive, + rootId, + [], + results, + ); } return results; @@ -294,25 +311,24 @@ type HoistableNode = TreeNode<'Optional' | 'NonNull'>; type DependencyNode = TreeNode; /** - * TODO: this is directly pasted from DeriveMinimalDependencies. Since we no - * longer have conditionally accessed nodes, we can simplify - * * Recursively calculates minimal dependencies in a subtree. * @param node DependencyNode representing a dependency subtree. * @returns a minimal list of dependencies in this subtree. */ function collectMinimalDependenciesInSubtree( node: DependencyNode, + reactive: boolean, rootIdentifier: Identifier, path: Array, results: Set, ): void { if (isDependency(node.accessType)) { - results.add({identifier: rootIdentifier, path}); + results.add({identifier: rootIdentifier, reactive, path}); } else { for (const [childName, childNode] of node.properties) { collectMinimalDependenciesInSubtree( childNode, + reactive, rootIdentifier, [ ...path, 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 99b8c189ee..1699a0fc3d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1568,6 +1568,18 @@ export type DependencyPathEntry = { export type DependencyPath = Array; export type ReactiveScopeDependency = { identifier: Identifier; + /** + * Reflects whether the base identifier is reactive. Note that some reactive + * objects may have non-reactive properties, but we do not currently track + * this. + * + * ```js + * // Technically, result[0] is reactive and result[1] is not. + * // Currently, both dependencies would be marked as reactive. + * const result = useState(); + * ``` + */ + reactive: boolean; path: DependencyPath; }; 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 96b9e51710..91b7712b88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -316,6 +316,7 @@ function collectTemporariesSidemapImpl( ) { temporaries.set(lvalue.identifier.id, { identifier: value.place.identifier, + reactive: value.place.reactive, path: [], }); } @@ -369,11 +370,13 @@ function getProperty( if (resolvedDependency == null) { property = { identifier: object.identifier, + reactive: object.reactive, path: [{property: propertyName, optional}], }; } else { property = { identifier: resolvedDependency.identifier, + reactive: resolvedDependency.reactive, path: [...resolvedDependency.path, {property: propertyName, optional}], }; } @@ -532,6 +535,7 @@ export class DependencyCollectionContext { this.visitDependency( this.#temporaries.get(place.identifier.id) ?? { identifier: place.identifier, + reactive: place.reactive, path: [], }, ); @@ -596,6 +600,7 @@ export class DependencyCollectionContext { ) { maybeDependency = { identifier: maybeDependency.identifier, + reactive: maybeDependency.reactive, path: [], }; } @@ -617,7 +622,11 @@ export class DependencyCollectionContext { identifier => identifier.declarationId === place.identifier.declarationId, ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) + this.#checkValidDependency({ + identifier: place.identifier, + reactive: place.reactive, + path: [], + }) ) { currentScope.reassignments.add(place.identifier); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts index b05b292124..88faccd8cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts @@ -26,6 +26,7 @@ import { import {PostDominator} from '../HIR/Dominator'; import { eachInstructionLValue, + eachInstructionOperand, eachInstructionValueOperand, eachTerminalOperand, } from '../HIR/visitors'; @@ -292,7 +293,7 @@ export function inferReactivePlaces(fn: HIRFunction): void { let hasReactiveInput = false; /* * NOTE: we want to mark all operands as reactive or not, so we - * avoid short-circuting here + * avoid short-circuiting here */ for (const operand of eachInstructionValueOperand(value)) { const reactive = reactiveIdentifiers.isReactive(operand); @@ -375,6 +376,41 @@ export function inferReactivePlaces(fn: HIRFunction): void { } } } while (reactiveIdentifiers.snapshot()); + + function propagateReactivityToInnerFunctions( + fn: HIRFunction, + isOutermost: boolean, + ): void { + for (const [, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if (!isOutermost) { + for (const operand of eachInstructionOperand(instr)) { + reactiveIdentifiers.isReactive(operand); + } + } + if ( + instr.value.kind === 'ObjectMethod' || + instr.value.kind === 'FunctionExpression' + ) { + propagateReactivityToInnerFunctions( + instr.value.loweredFunc.func, + false, + ); + } + } + if (!isOutermost) { + for (const operand of eachTerminalOperand(block.terminal)) { + reactiveIdentifiers.isReactive(operand); + } + } + } + } + + /** + * Propagate reactivity for inner functions, as we eventually hoist and dedupe + * dependency instructions for scopes. + */ + propagateReactivityToInnerFunctions(fn, true); } /* diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts index 08d2212d86..6f2d97ff8e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts @@ -456,6 +456,7 @@ function canMergeScopes( new Set( [...current.scope.declarations.values()].map(declaration => ({ identifier: declaration.identifier, + reactive: true, path: [], })), ), From 53cc92d2d21bebc261784306c04110a7a9734385 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 14:46:06 -0400 Subject: [PATCH 404/422] [compiler] Inferred effect dependencies now include optional chains Inferred effect dependencies now include optional chains. This is a temporary solution while https://github.com/facebook/react/pull/32099 and its followups are worked on. Ideally, we should model reactive scope dependencies in the IR similarly to `ComputeIR` -- dependencies should be hoisted and all references rewritten to use the hoisted dependencies. ` --- .../src/HIR/ScopeDependencyUtils.ts | 280 ++++++++++++++++++ .../src/Inference/InferEffectDependencies.ts | 278 +++++++++++------ ...e-after-useeffect-optional-chain.expect.md | 58 ++++ .../mutate-after-useeffect-optional-chain.js | 17 ++ ...utate-after-useeffect-ref-access.expect.md | 25 +- .../mutate-after-useeffect-ref-access.js | 7 +- .../mutate-after-useeffect.expect.md | 32 +- .../bailout-retry/mutate-after-useeffect.js | 11 +- .../reactive-optional-chain-complex.expect.md | 101 +++++++ .../reactive-optional-chain-complex.js | 18 ++ .../reactive-optional-chain.expect.md | 31 +- .../reactive-optional-chain.js | 8 +- 12 files changed, 754 insertions(+), 112 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts new file mode 100644 index 0000000000..b986cd211b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts @@ -0,0 +1,280 @@ +import { + Place, + ReactiveScopeDependency, + Identifier, + makeInstructionId, + InstructionKind, + GeneratedSource, + BlockId, + makeTemporaryIdentifier, + Effect, + GotoVariant, + HIR, +} from './HIR'; +import {CompilerError} from '../CompilerError'; +import {Environment} from './Environment'; +import HIRBuilder from './HIRBuilder'; +import {lowerValueToTemporary} from './BuildHIR'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; + +type DependencyInstructions = { + place: Place; + value: HIR; + exitBlockId: BlockId; +}; + +export function buildDependencyInstructions( + dep: ReactiveScopeDependency, + env: Environment, +): DependencyInstructions { + const builder = new HIRBuilder(env, { + entryBlockKind: 'value', + }); + let result: Place; + if (dep.path.every(path => !path.optional)) { + const last = writeNonOptionalDependency(dep, env, builder); + result = { + kind: 'Identifier', + identifier: last, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }; + } else { + const last = writeOptionalDependency( + dep.path.length - 1, + dep, + builder, + null, + ); + result = { + kind: 'Identifier', + identifier: last, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }; + } + + const exitBlockId = builder.terminate( + { + kind: 'unsupported', + loc: GeneratedSource, + id: makeInstructionId(0), + }, + null, + ); + return { + place: result, + value: builder.build(), + exitBlockId, + }; +} +function writeNonOptionalDependency( + dep: ReactiveScopeDependency, + env: Environment, + builder: HIRBuilder, +): Identifier { + const loc = dep.identifier.loc; + let last: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: last, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'LoadLocal', + place: { + identifier: dep.identifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + + for (const path of dep.path) { + const next = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: next, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'PropertyLoad', + object: { + identifier: last, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + property: path.property, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + last = next; + } + return last; +} + +function writeOptionalDependency( + idx: number, + dep: ReactiveScopeDependency, + builder: HIRBuilder, + parentAlternate: BlockId | null, +): Identifier { + const env = builder.environment; + CompilerError.invariant( + idx >= 0 && !dep.path.slice(0, idx + 1).every(path => !path.optional), + { + reason: '[WriteOptional] Expected optional path', + description: `${idx} ${printDependency(dep)}`, + loc: GeneratedSource, + }, + ); + const continuationBlock = builder.reserve(builder.currentBlockKind()); + const consequent = builder.reserve('value'); + + const returnPlace: Place = { + kind: 'Identifier', + identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource), + effect: Effect.Mutate, + reactive: dep.reactive, + loc: GeneratedSource, + }; + + let alternate; + if (parentAlternate != null) { + alternate = parentAlternate; + } else { + /** + * Make outermost alternate block + * $N = Primitive undefined + * $M = StoreLocal $OptionalResult = $N + * goto fallthrough + */ + alternate = builder.enter('value', () => { + const temp = lowerValueToTemporary(builder, { + kind: 'Primitive', + value: undefined, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...returnPlace}}, + value: {...temp}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + } + + let testIdentifier: Identifier | null = null; + const testBlock = builder.enter('value', () => { + const firstOptional = dep.path.findIndex(path => path.optional); + if (idx === firstOptional) { + // Lower test block + testIdentifier = writeNonOptionalDependency( + { + identifier: dep.identifier, + reactive: dep.reactive, + path: dep.path.slice(0, idx), + }, + env, + builder, + ); + } else { + testIdentifier = writeOptionalDependency( + idx - 1, + dep, + builder, + alternate, + ); + } + + return { + kind: 'branch', + test: { + identifier: testIdentifier, + effect: Effect.Freeze, + kind: 'Identifier', + loc: GeneratedSource, + reactive: dep.reactive, + }, + consequent: consequent.id, + alternate, + id: makeInstructionId(0), + loc: GeneratedSource, + fallthrough: continuationBlock.id, + }; + }); + + builder.enterReserved(consequent, () => { + CompilerError.invariant(testIdentifier !== null, { + reason: 'Satisfy type checker', + description: null, + loc: null, + suggestions: null, + }); + + const tmpConsequent = lowerValueToTemporary(builder, { + kind: 'PropertyLoad', + object: { + identifier: testIdentifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + property: dep.path[idx].property, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...returnPlace}}, + value: {...tmpConsequent}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + builder.terminateWithContinuation( + { + kind: 'optional', + optional: dep.path[idx].optional, + test: testBlock, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }, + continuationBlock, + ); + + return returnPlace.identifier; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index f1a5843419..722f9f7587 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -10,7 +10,6 @@ import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, Effect, - Environment, FunctionExpression, GeneratedSource, HIRFunction, @@ -29,6 +28,9 @@ import { isSetStateType, isFireFunctionType, makeScopeId, + HIR, + BasicBlock, + BlockId, } from '../HIR'; import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads'; import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies'; @@ -44,7 +46,12 @@ import { DependencyCollectionContext, handleInstruction, } from '../HIR/PropagateScopeDependenciesHIR'; -import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {buildDependencyInstructions} from '../HIR/ScopeDependencyUtils'; +import { + eachInstructionOperand, + eachTerminalOperand, + terminalFallthrough, +} from '../HIR/visitors'; import {empty} from '../Utils/Stack'; import {getOrInsertWith} from '../Utils/utils'; @@ -53,7 +60,6 @@ import {getOrInsertWith} from '../Utils/utils'; * a second argument to the useEffect call if no dependency array is provided. */ export function inferEffectDependencies(fn: HIRFunction): void { - let hasRewrite = false; const fnExpressions = new Map< IdentifierId, TInstruction @@ -86,6 +92,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) */ const reactiveIds = inferReactiveIdentifiers(fn); + const rewriteBlocks: Array = []; for (const [, block] of fn.body.blocks) { if (block.terminal.kind === 'scope') { @@ -101,7 +108,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { ); } } - const rewriteInstrs = new Map>(); + const rewriteInstrs: Array = []; for (const instr of block.instructions) { const {value, lvalue} = instr; if (value.kind === 'FunctionExpression') { @@ -165,7 +172,6 @@ export function inferEffectDependencies(fn: HIRFunction): void { ) { // We have a useEffect call with no deps array, so we need to infer the deps const effectDeps: Array = []; - const newInstructions: Array = []; const deps: ArrayExpression = { kind: 'ArrayExpression', elements: effectDeps, @@ -196,24 +202,28 @@ export function inferEffectDependencies(fn: HIRFunction): void { */ const usedDeps = []; - for (const dep of minimalDeps) { + for (const maybeDep of minimalDeps) { if ( - ((isUseRefType(dep.identifier) || - isSetStateType(dep.identifier)) && - !reactiveIds.has(dep.identifier.id)) || - isFireFunctionType(dep.identifier) + ((isUseRefType(maybeDep.identifier) || + isSetStateType(maybeDep.identifier)) && + !reactiveIds.has(maybeDep.identifier.id)) || + isFireFunctionType(maybeDep.identifier) ) { // exclude non-reactive hook results, which will never be in a memo block continue; } - const {place, instructions} = writeDependencyToInstructions( + const dep = truncateDepAtCurrent(maybeDep); + const {place, value, exitBlockId} = buildDependencyInstructions( dep, - reactiveIds.has(dep.identifier.id), fn.env, - fnExpr.loc, ); - newInstructions.push(...instructions); + rewriteInstrs.push({ + kind: 'block', + location: instr.id, + value, + exitBlockId: exitBlockId, + }); effectDeps.push(place); usedDeps.push(dep); } @@ -234,27 +244,32 @@ export function inferEffectDependencies(fn: HIRFunction): void { }); } - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, - }); - // Step 2: push the inferred deps array as an argument of the useEffect + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, + }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } else if (loadGlobals.has(value.args[0].identifier.id)) { // Global functions have no reactive dependencies, so we can insert an empty array - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } } else if ( @@ -285,21 +300,13 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } } - if (rewriteInstrs.size > 0) { - hasRewrite = true; - const newInstrs = []; - for (const instr of block.instructions) { - const newInstr = rewriteInstrs.get(instr.id); - if (newInstr != null) { - newInstrs.push(...newInstr, instr); - } else { - newInstrs.push(instr); - } - } - block.instructions = newInstrs; - } + rewriteSplices(block, rewriteInstrs, rewriteBlocks); } - if (hasRewrite) { + + if (rewriteBlocks.length > 0) { + for (const block of rewriteBlocks) { + fn.body.blocks.set(block.id, block); + } // Renumber instructions and fix scope ranges markInstructionIds(fn.body); fixScopeAndIdentifierRanges(fn.body); @@ -307,63 +314,144 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } -function writeDependencyToInstructions( +function truncateDepAtCurrent( dep: ReactiveScopeDependency, - reactive: boolean, - env: Environment, - loc: SourceLocation, -): {place: Place; instructions: Array} { - const instructions: Array = []; - let currValue = createTemporaryPlace(env, GeneratedSource); - currValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...currValue, effect: Effect.Mutate}, - value: { - kind: 'LoadLocal', - place: { - kind: 'Identifier', - identifier: dep.identifier, - effect: Effect.Capture, - reactive, - loc: loc, - }, - loc: loc, - }, - }); - for (const path of dep.path) { - if (path.optional) { - /** - * TODO: instead of truncating optional paths, reuse - * instructions from hoisted dependencies block(s) - */ - break; +): ReactiveScopeDependency { + const idx = dep.path.findIndex(path => path.property === 'current'); + if (idx === -1) { + return dep; + } else { + return {...dep, path: dep.path.slice(0, idx)}; + } +} + +type SpliceInfo = + | {kind: 'instr'; location: InstructionId; value: Instruction} + | { + kind: 'block'; + location: InstructionId; + value: HIR; + exitBlockId: BlockId; + }; + +function rewriteSplices( + originalBlock: BasicBlock, + splices: Array, + rewriteBlocks: Array, +): void { + if (splices.length === 0) { + return; + } + /** + * Splice instructions or value blocks into the original block. + * --- original block --- + * bb_original + * instr1 + * ... + * instr2 <-- splice location + * instr3 + * ... + * + * + * If there is more than one block in the splice, this means that we're + * splicing in a set of value-blocks of the following structure: + * --- blocks we're splicing in --- + * bb_entry: + * instrEntry + * ... + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... + * + * + * + * --- rewritten blocks --- + * bb_original + * instr1 + * ... (original instructions) + * instr2 + * instrEntry + * ... (spliced instructions) + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... (spliced instructions) + * instr3 + * ... (original instructions) + * + */ + const originalInstrs = originalBlock.instructions; + let currBlock: BasicBlock = {...originalBlock, instructions: []}; + rewriteBlocks.push(currBlock); + + let cursor = 0; + for (const rewrite of splices) { + while (originalInstrs[cursor].id < rewrite.location) { + CompilerError.invariant( + originalInstrs[cursor].id < originalInstrs[cursor + 1].id, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted', + loc: originalInstrs[cursor].loc, + }, + ); + currBlock.instructions.push(originalInstrs[cursor]); + cursor++; } - if (path.property === 'current') { - /* - * Prune ref.current accesses. This may over-capture for non-ref values with - * a current property, but that's fine. - */ - break; + CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, { + reason: + '[InferEffectDependencies] Internal invariant broken: splice location not found', + loc: originalInstrs[cursor].loc, + }); + + if (rewrite.kind === 'instr') { + currBlock.instructions.push(rewrite.value); + } else { + const {entry, blocks} = rewrite.value; + const entryBlock = blocks.get(entry)!; + // splice in all instructions from the entry block + currBlock.instructions.push(...entryBlock.instructions); + if (blocks.size > 1) { + /** + * We're splicing in a set of value-blocks, which means we need + * to push new blocks and update terminals. + */ + CompilerError.invariant( + terminalFallthrough(entryBlock.terminal) === rewrite.exitBlockId, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough', + loc: entryBlock.terminal.loc, + }, + ); + const originalTerminal = currBlock.terminal; + currBlock.terminal = entryBlock.terminal; + + for (const [id, block] of blocks) { + if (id === entry) { + continue; + } + if (id === rewrite.exitBlockId) { + block.terminal = originalTerminal; + currBlock = block; + } + rewriteBlocks.push(block); + } + } } - const nextValue = createTemporaryPlace(env, GeneratedSource); - nextValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...nextValue, effect: Effect.Mutate}, - value: { - kind: 'PropertyLoad', - object: {...currValue, effect: Effect.Capture}, - property: path.property, - loc: loc, - }, - }); - currValue = nextValue; + } + for (let i = cursor; i < originalInstrs.length; i++) { + currBlock.instructions.push(originalInstrs[i]); } - currValue.effect = Effect.Freeze; - return {place: currValue, instructions}; } function inferReactiveIdentifiers(fn: HIRFunction): Set { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md new file mode 100644 index 0000000000..e4560848dd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +function Component(t0) { + const { foo } = t0; + const arr = []; + + useEffect(() => print(arr[0]?.value), [arr[0]?.value]); + arr.push({ value: foo }); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":304},"end":{"line":9,"column":39,"index":341},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":326},"end":{"line":9,"column":27,"index":329},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) [{"value":1}] +logs: [1] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js new file mode 100644 index 0000000000..c435b72d1a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js @@ -0,0 +1,17 @@ +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index 05ef40c150..5e6f19dd83 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -14,12 +14,17 @@ function Component({arrRef}) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect, useRef } from "react"; import { print } from "shared-runtime"; @@ -32,7 +37,21 @@ function Component(t0) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ arrRef: { current: { val: "initial ref value" } } }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":227},"end":{"line":8,"column":40,"index":265},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":249},"end":{"line":8,"column":30,"index":255},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) {"current":{"val":2}} +logs: [{ val: 2 }] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js index f497d7e595..bd3f6d1de5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -9,3 +9,8 @@ function Component({arrRef}) { arrRef.current.val = 2; return arrRef; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index fa1df3ef88..3b61fbf834 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -2,33 +2,55 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect } from "react"; function Component(t0) { const { foo } = t0; const arr = []; - useEffect(() => arr.push(foo), [arr, foo]); + useEffect(() => { + arr.push(foo); + }, [arr, foo]); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":149},"end":{"line":8,"column":4,"index":190},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":180},"end":{"line":7,"column":16,"index":183},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) [2] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js index 2e2eb7bc08..fbcbf004a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js @@ -1,9 +1,16 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md new file mode 100644 index 0000000000..be107a2afa --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md @@ -0,0 +1,101 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print, shallowCopy } from "shared-runtime"; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr(t0) { + const $ = _c(13); + const { cond, propVal } = t0; + let t1; + if ($[0] !== cond || $[1] !== propVal) { + t1 = cond ? { b: propVal } : null; + $[0] = cond; + $[1] = propVal; + $[2] = t1; + } else { + t1 = $[2]; + } + let t2; + if ($[3] !== t1) { + t2 = { a: t1, c: null }; + $[3] = t1; + $[4] = t2; + } else { + t2 = $[4]; + } + const obj = t2; + const t3 = propVal + 1; + let t4; + if ($[5] !== t3) { + t4 = shallowCopy({ a: { b: { c: { d: { e: { f: t3 } } } } } }); + $[5] = t3; + $[6] = t4; + } else { + t4 = $[6]; + } + const other = t4; + let t5; + if ($[7] !== propVal) { + t5 = shallowCopy(propVal); + $[7] = propVal; + $[8] = t5; + } else { + t5 = $[8]; + } + const primitive = t5; + let t6; + if ( + $[9] !== obj.a?.b || + $[10] !== other?.a?.b?.c?.d?.e.f || + $[11] !== primitive.a?.b.c?.d?.e.f + ) { + t6 = () => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f); + $[9] = obj.a?.b; + $[10] = other?.a?.b?.c?.d?.e.f; + $[11] = primitive.a?.b.c?.d?.e.f; + $[12] = t6; + } else { + t6 = $[12]; + } + useEffect(t6, [obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + +``` + +### Eval output +(kind: ok) +logs: [1,2,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js new file mode 100644 index 0000000000..f6e5a18d47 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js @@ -0,0 +1,18 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md index 7c9f21b85c..d2414f3f9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md @@ -8,10 +8,16 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + ``` ## Code @@ -23,7 +29,7 @@ import { print } from "shared-runtime"; // TODO: take optional chains as dependencies function ReactiveMemberExpr(t0) { - const $ = _c(7); + const $ = _c(9); const { cond, propVal } = t0; let t1; if ($[0] !== cond || $[1] !== propVal) { @@ -36,7 +42,7 @@ function ReactiveMemberExpr(t0) { } let t2; if ($[3] !== t1) { - t2 = { a: t1 }; + t2 = { a: t1, c: null }; $[3] = t1; $[4] = t2; } else { @@ -51,10 +57,25 @@ function ReactiveMemberExpr(t0) { } else { t3 = $[6]; } - useEffect(t3, [obj.a]); + useEffect(t3, [obj.a?.b]); + let t4; + if ($[7] !== obj.c?.d) { + t4 = () => print(obj.c?.d); + $[7] = obj.c?.d; + $[8] = t4; + } else { + t4 = $[8]; + } + useEffect(t4, [obj.c?.d]); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) +logs: [1,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js index 8a76784e24..4f567fa152 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js @@ -4,6 +4,12 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; From ab7d916bfaccf1faafc4ebeb3269f2159af0f498 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 15:06:10 -0400 Subject: [PATCH 405/422] [compiler] Inferred effect dependencies now include optional chains Inferred effect dependencies now include optional chains. This is a temporary solution while https://github.com/facebook/react/pull/32099 and its followups are worked on. Ideally, we should model reactive scope dependencies in the IR similarly to `ComputeIR` -- dependencies should be hoisted and all references rewritten to use the hoisted dependencies. ` --- .../src/HIR/ScopeDependencyUtils.ts | 286 ++++++++++++++++++ .../src/Inference/InferEffectDependencies.ts | 278 +++++++++++------ ...e-after-useeffect-optional-chain.expect.md | 58 ++++ .../mutate-after-useeffect-optional-chain.js | 17 ++ ...utate-after-useeffect-ref-access.expect.md | 25 +- .../mutate-after-useeffect-ref-access.js | 7 +- .../mutate-after-useeffect.expect.md | 32 +- .../bailout-retry/mutate-after-useeffect.js | 11 +- .../reactive-optional-chain-complex.expect.md | 101 +++++++ .../reactive-optional-chain-complex.js | 18 ++ .../reactive-optional-chain.expect.md | 31 +- .../reactive-optional-chain.js | 8 +- 12 files changed, 760 insertions(+), 112 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts new file mode 100644 index 0000000000..74feee508b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts @@ -0,0 +1,286 @@ +import { + Place, + ReactiveScopeDependency, + Identifier, + makeInstructionId, + InstructionKind, + GeneratedSource, + BlockId, + makeTemporaryIdentifier, + Effect, + GotoVariant, + HIR, +} from './HIR'; +import {CompilerError} from '../CompilerError'; +import {Environment} from './Environment'; +import HIRBuilder from './HIRBuilder'; +import {lowerValueToTemporary} from './BuildHIR'; + +type DependencyInstructions = { + place: Place; + value: HIR; + exitBlockId: BlockId; +}; + +export function buildDependencyInstructions( + dep: ReactiveScopeDependency, + env: Environment, +): DependencyInstructions { + const builder = new HIRBuilder(env, { + entryBlockKind: 'value', + }); + let result: Place; + if (dep.path.every(path => !path.optional)) { + const last = writeNonOptionalDependency(dep, env, builder); + result = { + kind: 'Identifier', + identifier: last, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }; + } else { + const last = writeOptionalDependency(dep, builder, null); + result = { + kind: 'Identifier', + identifier: last, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }; + } + + const exitBlockId = builder.terminate( + { + kind: 'unsupported', + loc: GeneratedSource, + id: makeInstructionId(0), + }, + null, + ); + return { + place: result, + value: builder.build(), + exitBlockId, + }; +} +function writeNonOptionalDependency( + dep: ReactiveScopeDependency, + env: Environment, + builder: HIRBuilder, +): Identifier { + const loc = dep.identifier.loc; + let last: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: last, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'LoadLocal', + place: { + identifier: dep.identifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + + for (const path of dep.path) { + const next = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: next, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'PropertyLoad', + object: { + identifier: last, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + property: path.property, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + last = next; + } + return last; +} + +/** + * Write a dependency into optional blocks if there is an `optional` anywhere + * along its path. + * + * e.g. `a.b?.c.d` is written to an optional block that tests `a.b` and + * conditionally evaluates `c.d`. + */ +function writeOptionalDependency( + dep: ReactiveScopeDependency, + builder: HIRBuilder, + parentAlternate: BlockId | null, +): Identifier { + const env = builder.environment; + + CompilerError.invariant( + dep.path.some(path => path.optional), + { + reason: + '[ScopeDependencyUtils] internal invariant broken: expected optional path', + loc: GeneratedSource, + }, + ); + /** + * Reserve an identifier which will be used to store the result of this + * dependency. + */ + const dependencyValue: Place = { + kind: 'Identifier', + identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource), + effect: Effect.Mutate, + reactive: dep.reactive, + loc: GeneratedSource, + }; + + /** + * Reserve a block which is the fallthrough (and transitive successor) of this + * optional chain. + */ + const continuationBlock = builder.reserve(builder.currentBlockKind()); + let alternate; + if (parentAlternate != null) { + alternate = parentAlternate; + } else { + /** + * If an outermost alternate block has not been reserved, write one + * + * $N = Primitive undefined + * $M = StoreLocal $OptionalResult = $N + * goto fallthrough + */ + alternate = builder.enter('value', () => { + const temp = lowerValueToTemporary(builder, { + kind: 'Primitive', + value: undefined, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}}, + value: {...temp}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + } + + // Reserve the consequent block, which is the successor of the test block + const consequent = builder.reserve('value'); + + let testIdentifier: Identifier | null = null; + const testBlock = builder.enter('value', () => { + const testDependency = { + ...dep, + path: dep.path.slice(0, dep.path.length - 1), + }; + const firstOptional = dep.path.findIndex(path => path.optional); + if (firstOptional === dep.path.length - 1) { + // Base case: the test block is simple + testIdentifier = writeNonOptionalDependency(testDependency, env, builder); + } else { + // Otherwise, the test block is a nested optional chain + testIdentifier = writeOptionalDependency( + testDependency, + builder, + alternate, + ); + } + + return { + kind: 'branch', + test: { + identifier: testIdentifier, + effect: Effect.Freeze, + kind: 'Identifier', + loc: GeneratedSource, + reactive: dep.reactive, + }, + consequent: consequent.id, + alternate, + id: makeInstructionId(0), + loc: GeneratedSource, + fallthrough: continuationBlock.id, + }; + }); + + builder.enterReserved(consequent, () => { + CompilerError.invariant(testIdentifier !== null, { + reason: 'Satisfy type checker', + description: null, + loc: null, + suggestions: null, + }); + + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}}, + value: lowerValueToTemporary(builder, { + kind: 'PropertyLoad', + object: { + identifier: testIdentifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + property: dep.path.at(-1)!.property, + loc: GeneratedSource, + }), + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + builder.terminateWithContinuation( + { + kind: 'optional', + optional: dep.path.at(-1)!.optional, + test: testBlock, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }, + continuationBlock, + ); + + return dependencyValue.identifier; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index f1a5843419..722f9f7587 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -10,7 +10,6 @@ import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, Effect, - Environment, FunctionExpression, GeneratedSource, HIRFunction, @@ -29,6 +28,9 @@ import { isSetStateType, isFireFunctionType, makeScopeId, + HIR, + BasicBlock, + BlockId, } from '../HIR'; import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads'; import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies'; @@ -44,7 +46,12 @@ import { DependencyCollectionContext, handleInstruction, } from '../HIR/PropagateScopeDependenciesHIR'; -import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {buildDependencyInstructions} from '../HIR/ScopeDependencyUtils'; +import { + eachInstructionOperand, + eachTerminalOperand, + terminalFallthrough, +} from '../HIR/visitors'; import {empty} from '../Utils/Stack'; import {getOrInsertWith} from '../Utils/utils'; @@ -53,7 +60,6 @@ import {getOrInsertWith} from '../Utils/utils'; * a second argument to the useEffect call if no dependency array is provided. */ export function inferEffectDependencies(fn: HIRFunction): void { - let hasRewrite = false; const fnExpressions = new Map< IdentifierId, TInstruction @@ -86,6 +92,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) */ const reactiveIds = inferReactiveIdentifiers(fn); + const rewriteBlocks: Array = []; for (const [, block] of fn.body.blocks) { if (block.terminal.kind === 'scope') { @@ -101,7 +108,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { ); } } - const rewriteInstrs = new Map>(); + const rewriteInstrs: Array = []; for (const instr of block.instructions) { const {value, lvalue} = instr; if (value.kind === 'FunctionExpression') { @@ -165,7 +172,6 @@ export function inferEffectDependencies(fn: HIRFunction): void { ) { // We have a useEffect call with no deps array, so we need to infer the deps const effectDeps: Array = []; - const newInstructions: Array = []; const deps: ArrayExpression = { kind: 'ArrayExpression', elements: effectDeps, @@ -196,24 +202,28 @@ export function inferEffectDependencies(fn: HIRFunction): void { */ const usedDeps = []; - for (const dep of minimalDeps) { + for (const maybeDep of minimalDeps) { if ( - ((isUseRefType(dep.identifier) || - isSetStateType(dep.identifier)) && - !reactiveIds.has(dep.identifier.id)) || - isFireFunctionType(dep.identifier) + ((isUseRefType(maybeDep.identifier) || + isSetStateType(maybeDep.identifier)) && + !reactiveIds.has(maybeDep.identifier.id)) || + isFireFunctionType(maybeDep.identifier) ) { // exclude non-reactive hook results, which will never be in a memo block continue; } - const {place, instructions} = writeDependencyToInstructions( + const dep = truncateDepAtCurrent(maybeDep); + const {place, value, exitBlockId} = buildDependencyInstructions( dep, - reactiveIds.has(dep.identifier.id), fn.env, - fnExpr.loc, ); - newInstructions.push(...instructions); + rewriteInstrs.push({ + kind: 'block', + location: instr.id, + value, + exitBlockId: exitBlockId, + }); effectDeps.push(place); usedDeps.push(dep); } @@ -234,27 +244,32 @@ export function inferEffectDependencies(fn: HIRFunction): void { }); } - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, - }); - // Step 2: push the inferred deps array as an argument of the useEffect + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, + }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } else if (loadGlobals.has(value.args[0].identifier.id)) { // Global functions have no reactive dependencies, so we can insert an empty array - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } } else if ( @@ -285,21 +300,13 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } } - if (rewriteInstrs.size > 0) { - hasRewrite = true; - const newInstrs = []; - for (const instr of block.instructions) { - const newInstr = rewriteInstrs.get(instr.id); - if (newInstr != null) { - newInstrs.push(...newInstr, instr); - } else { - newInstrs.push(instr); - } - } - block.instructions = newInstrs; - } + rewriteSplices(block, rewriteInstrs, rewriteBlocks); } - if (hasRewrite) { + + if (rewriteBlocks.length > 0) { + for (const block of rewriteBlocks) { + fn.body.blocks.set(block.id, block); + } // Renumber instructions and fix scope ranges markInstructionIds(fn.body); fixScopeAndIdentifierRanges(fn.body); @@ -307,63 +314,144 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } -function writeDependencyToInstructions( +function truncateDepAtCurrent( dep: ReactiveScopeDependency, - reactive: boolean, - env: Environment, - loc: SourceLocation, -): {place: Place; instructions: Array} { - const instructions: Array = []; - let currValue = createTemporaryPlace(env, GeneratedSource); - currValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...currValue, effect: Effect.Mutate}, - value: { - kind: 'LoadLocal', - place: { - kind: 'Identifier', - identifier: dep.identifier, - effect: Effect.Capture, - reactive, - loc: loc, - }, - loc: loc, - }, - }); - for (const path of dep.path) { - if (path.optional) { - /** - * TODO: instead of truncating optional paths, reuse - * instructions from hoisted dependencies block(s) - */ - break; +): ReactiveScopeDependency { + const idx = dep.path.findIndex(path => path.property === 'current'); + if (idx === -1) { + return dep; + } else { + return {...dep, path: dep.path.slice(0, idx)}; + } +} + +type SpliceInfo = + | {kind: 'instr'; location: InstructionId; value: Instruction} + | { + kind: 'block'; + location: InstructionId; + value: HIR; + exitBlockId: BlockId; + }; + +function rewriteSplices( + originalBlock: BasicBlock, + splices: Array, + rewriteBlocks: Array, +): void { + if (splices.length === 0) { + return; + } + /** + * Splice instructions or value blocks into the original block. + * --- original block --- + * bb_original + * instr1 + * ... + * instr2 <-- splice location + * instr3 + * ... + * + * + * If there is more than one block in the splice, this means that we're + * splicing in a set of value-blocks of the following structure: + * --- blocks we're splicing in --- + * bb_entry: + * instrEntry + * ... + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... + * + * + * + * --- rewritten blocks --- + * bb_original + * instr1 + * ... (original instructions) + * instr2 + * instrEntry + * ... (spliced instructions) + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... (spliced instructions) + * instr3 + * ... (original instructions) + * + */ + const originalInstrs = originalBlock.instructions; + let currBlock: BasicBlock = {...originalBlock, instructions: []}; + rewriteBlocks.push(currBlock); + + let cursor = 0; + for (const rewrite of splices) { + while (originalInstrs[cursor].id < rewrite.location) { + CompilerError.invariant( + originalInstrs[cursor].id < originalInstrs[cursor + 1].id, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted', + loc: originalInstrs[cursor].loc, + }, + ); + currBlock.instructions.push(originalInstrs[cursor]); + cursor++; } - if (path.property === 'current') { - /* - * Prune ref.current accesses. This may over-capture for non-ref values with - * a current property, but that's fine. - */ - break; + CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, { + reason: + '[InferEffectDependencies] Internal invariant broken: splice location not found', + loc: originalInstrs[cursor].loc, + }); + + if (rewrite.kind === 'instr') { + currBlock.instructions.push(rewrite.value); + } else { + const {entry, blocks} = rewrite.value; + const entryBlock = blocks.get(entry)!; + // splice in all instructions from the entry block + currBlock.instructions.push(...entryBlock.instructions); + if (blocks.size > 1) { + /** + * We're splicing in a set of value-blocks, which means we need + * to push new blocks and update terminals. + */ + CompilerError.invariant( + terminalFallthrough(entryBlock.terminal) === rewrite.exitBlockId, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough', + loc: entryBlock.terminal.loc, + }, + ); + const originalTerminal = currBlock.terminal; + currBlock.terminal = entryBlock.terminal; + + for (const [id, block] of blocks) { + if (id === entry) { + continue; + } + if (id === rewrite.exitBlockId) { + block.terminal = originalTerminal; + currBlock = block; + } + rewriteBlocks.push(block); + } + } } - const nextValue = createTemporaryPlace(env, GeneratedSource); - nextValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...nextValue, effect: Effect.Mutate}, - value: { - kind: 'PropertyLoad', - object: {...currValue, effect: Effect.Capture}, - property: path.property, - loc: loc, - }, - }); - currValue = nextValue; + } + for (let i = cursor; i < originalInstrs.length; i++) { + currBlock.instructions.push(originalInstrs[i]); } - currValue.effect = Effect.Freeze; - return {place: currValue, instructions}; } function inferReactiveIdentifiers(fn: HIRFunction): Set { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md new file mode 100644 index 0000000000..e4560848dd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +function Component(t0) { + const { foo } = t0; + const arr = []; + + useEffect(() => print(arr[0]?.value), [arr[0]?.value]); + arr.push({ value: foo }); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":304},"end":{"line":9,"column":39,"index":341},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":326},"end":{"line":9,"column":27,"index":329},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) [{"value":1}] +logs: [1] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js new file mode 100644 index 0000000000..c435b72d1a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js @@ -0,0 +1,17 @@ +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index 05ef40c150..5e6f19dd83 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -14,12 +14,17 @@ function Component({arrRef}) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect, useRef } from "react"; import { print } from "shared-runtime"; @@ -32,7 +37,21 @@ function Component(t0) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ arrRef: { current: { val: "initial ref value" } } }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":227},"end":{"line":8,"column":40,"index":265},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":249},"end":{"line":8,"column":30,"index":255},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) {"current":{"val":2}} +logs: [{ val: 2 }] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js index f497d7e595..bd3f6d1de5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -9,3 +9,8 @@ function Component({arrRef}) { arrRef.current.val = 2; return arrRef; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index fa1df3ef88..3b61fbf834 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -2,33 +2,55 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect } from "react"; function Component(t0) { const { foo } = t0; const arr = []; - useEffect(() => arr.push(foo), [arr, foo]); + useEffect(() => { + arr.push(foo); + }, [arr, foo]); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":149},"end":{"line":8,"column":4,"index":190},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":180},"end":{"line":7,"column":16,"index":183},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) [2] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js index 2e2eb7bc08..fbcbf004a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js @@ -1,9 +1,16 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md new file mode 100644 index 0000000000..be107a2afa --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md @@ -0,0 +1,101 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print, shallowCopy } from "shared-runtime"; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr(t0) { + const $ = _c(13); + const { cond, propVal } = t0; + let t1; + if ($[0] !== cond || $[1] !== propVal) { + t1 = cond ? { b: propVal } : null; + $[0] = cond; + $[1] = propVal; + $[2] = t1; + } else { + t1 = $[2]; + } + let t2; + if ($[3] !== t1) { + t2 = { a: t1, c: null }; + $[3] = t1; + $[4] = t2; + } else { + t2 = $[4]; + } + const obj = t2; + const t3 = propVal + 1; + let t4; + if ($[5] !== t3) { + t4 = shallowCopy({ a: { b: { c: { d: { e: { f: t3 } } } } } }); + $[5] = t3; + $[6] = t4; + } else { + t4 = $[6]; + } + const other = t4; + let t5; + if ($[7] !== propVal) { + t5 = shallowCopy(propVal); + $[7] = propVal; + $[8] = t5; + } else { + t5 = $[8]; + } + const primitive = t5; + let t6; + if ( + $[9] !== obj.a?.b || + $[10] !== other?.a?.b?.c?.d?.e.f || + $[11] !== primitive.a?.b.c?.d?.e.f + ) { + t6 = () => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f); + $[9] = obj.a?.b; + $[10] = other?.a?.b?.c?.d?.e.f; + $[11] = primitive.a?.b.c?.d?.e.f; + $[12] = t6; + } else { + t6 = $[12]; + } + useEffect(t6, [obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + +``` + +### Eval output +(kind: ok) +logs: [1,2,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js new file mode 100644 index 0000000000..f6e5a18d47 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js @@ -0,0 +1,18 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md index 7c9f21b85c..d2414f3f9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md @@ -8,10 +8,16 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + ``` ## Code @@ -23,7 +29,7 @@ import { print } from "shared-runtime"; // TODO: take optional chains as dependencies function ReactiveMemberExpr(t0) { - const $ = _c(7); + const $ = _c(9); const { cond, propVal } = t0; let t1; if ($[0] !== cond || $[1] !== propVal) { @@ -36,7 +42,7 @@ function ReactiveMemberExpr(t0) { } let t2; if ($[3] !== t1) { - t2 = { a: t1 }; + t2 = { a: t1, c: null }; $[3] = t1; $[4] = t2; } else { @@ -51,10 +57,25 @@ function ReactiveMemberExpr(t0) { } else { t3 = $[6]; } - useEffect(t3, [obj.a]); + useEffect(t3, [obj.a?.b]); + let t4; + if ($[7] !== obj.c?.d) { + t4 = () => print(obj.c?.d); + $[7] = obj.c?.d; + $[8] = t4; + } else { + t4 = $[8]; + } + useEffect(t4, [obj.c?.d]); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) +logs: [1,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js index 8a76784e24..4f567fa152 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js @@ -4,6 +4,12 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; From fe7e4c89e893e669d3ad09cf67b1334c288238a4 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 15:06:10 -0400 Subject: [PATCH 406/422] [compiler] Inferred effect dependencies now include optional chains Inferred effect dependencies now include optional chains. This is a temporary solution while https://github.com/facebook/react/pull/32099 and its followups are worked on. Ideally, we should model reactive scope dependencies in the IR similarly to `ComputeIR` -- dependencies should be hoisted and all references rewritten to use the hoisted dependencies. ` --- .../src/HIR/ScopeDependencyUtils.ts | 283 ++++++++++++++++++ .../src/Inference/InferEffectDependencies.ts | 278 +++++++++++------ ...e-after-useeffect-optional-chain.expect.md | 58 ++++ .../mutate-after-useeffect-optional-chain.js | 17 ++ ...utate-after-useeffect-ref-access.expect.md | 25 +- .../mutate-after-useeffect-ref-access.js | 7 +- .../mutate-after-useeffect.expect.md | 32 +- .../bailout-retry/mutate-after-useeffect.js | 11 +- .../reactive-optional-chain-complex.expect.md | 101 +++++++ .../reactive-optional-chain-complex.js | 18 ++ .../reactive-optional-chain.expect.md | 31 +- .../reactive-optional-chain.js | 8 +- 12 files changed, 757 insertions(+), 112 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts new file mode 100644 index 0000000000..5d30aeb644 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts @@ -0,0 +1,283 @@ +import { + Place, + ReactiveScopeDependency, + Identifier, + makeInstructionId, + InstructionKind, + GeneratedSource, + BlockId, + makeTemporaryIdentifier, + Effect, + GotoVariant, + HIR, +} from './HIR'; +import {CompilerError} from '../CompilerError'; +import {Environment} from './Environment'; +import HIRBuilder from './HIRBuilder'; +import {lowerValueToTemporary} from './BuildHIR'; + +type DependencyInstructions = { + place: Place; + value: HIR; + exitBlockId: BlockId; +}; + +export function buildDependencyInstructions( + dep: ReactiveScopeDependency, + env: Environment, +): DependencyInstructions { + const builder = new HIRBuilder(env, { + entryBlockKind: 'value', + }); + let dependencyValue: Identifier; + if (dep.path.every(path => !path.optional)) { + dependencyValue = writeNonOptionalDependency(dep, env, builder); + } else { + dependencyValue = writeOptionalDependency(dep, builder, null); + } + + const exitBlockId = builder.terminate( + { + kind: 'unsupported', + loc: GeneratedSource, + id: makeInstructionId(0), + }, + null, + ); + return { + place: { + kind: 'Identifier', + identifier: dependencyValue, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + value: builder.build(), + exitBlockId, + }; +} + +/** + * Write instructions for a simple dependency (without optional chains) + */ +function writeNonOptionalDependency( + dep: ReactiveScopeDependency, + env: Environment, + builder: HIRBuilder, +): Identifier { + const loc = dep.identifier.loc; + let curr: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: curr, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'LoadLocal', + place: { + identifier: dep.identifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + + /** + * Iteratively build up dependency instructions by reading from the last written + * instruction. + */ + for (const path of dep.path) { + const next = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: next, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'PropertyLoad', + object: { + identifier: curr, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + property: path.property, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + curr = next; + } + return curr; +} + +/** + * Write a dependency into optional blocks. + * + * e.g. `a.b?.c.d` is written to an optional block that tests `a.b` and + * conditionally evaluates `c.d`. + */ +function writeOptionalDependency( + dep: ReactiveScopeDependency, + builder: HIRBuilder, + parentAlternate: BlockId | null, +): Identifier { + const env = builder.environment; + /** + * Reserve an identifier which will be used to store the result of this + * dependency. + */ + const dependencyValue: Place = { + kind: 'Identifier', + identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource), + effect: Effect.Mutate, + reactive: dep.reactive, + loc: GeneratedSource, + }; + + /** + * Reserve a block which is the fallthrough (and transitive successor) of this + * optional chain. + */ + const continuationBlock = builder.reserve(builder.currentBlockKind()); + let alternate; + if (parentAlternate != null) { + alternate = parentAlternate; + } else { + /** + * If an outermost alternate block has not been reserved, write one + * + * $N = Primitive undefined + * $M = StoreLocal $OptionalResult = $N + * goto fallthrough + */ + alternate = builder.enter('value', () => { + const temp = lowerValueToTemporary(builder, { + kind: 'Primitive', + value: undefined, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}}, + value: {...temp}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + } + + // Reserve the consequent block, which is the successor of the test block + const consequent = builder.reserve('value'); + + let testIdentifier: Identifier | null = null; + const testBlock = builder.enter('value', () => { + const testDependency = { + ...dep, + path: dep.path.slice(0, dep.path.length - 1), + }; + const firstOptional = dep.path.findIndex(path => path.optional); + CompilerError.invariant(firstOptional !== -1, { + reason: + '[ScopeDependencyUtils] Internal invariant broken: expected optional path', + loc: dep.identifier.loc, + description: null, + suggestions: null, + }); + if (firstOptional === dep.path.length - 1) { + // Base case: the test block is simple + testIdentifier = writeNonOptionalDependency(testDependency, env, builder); + } else { + // Otherwise, the test block is a nested optional chain + testIdentifier = writeOptionalDependency( + testDependency, + builder, + alternate, + ); + } + + return { + kind: 'branch', + test: { + identifier: testIdentifier, + effect: Effect.Freeze, + kind: 'Identifier', + loc: GeneratedSource, + reactive: dep.reactive, + }, + consequent: consequent.id, + alternate, + id: makeInstructionId(0), + loc: GeneratedSource, + fallthrough: continuationBlock.id, + }; + }); + + builder.enterReserved(consequent, () => { + CompilerError.invariant(testIdentifier !== null, { + reason: 'Satisfy type checker', + description: null, + loc: null, + suggestions: null, + }); + + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}}, + value: lowerValueToTemporary(builder, { + kind: 'PropertyLoad', + object: { + identifier: testIdentifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + property: dep.path.at(-1)!.property, + loc: GeneratedSource, + }), + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + builder.terminateWithContinuation( + { + kind: 'optional', + optional: dep.path.at(-1)!.optional, + test: testBlock, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }, + continuationBlock, + ); + + return dependencyValue.identifier; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index f1a5843419..722f9f7587 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -10,7 +10,6 @@ import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, Effect, - Environment, FunctionExpression, GeneratedSource, HIRFunction, @@ -29,6 +28,9 @@ import { isSetStateType, isFireFunctionType, makeScopeId, + HIR, + BasicBlock, + BlockId, } from '../HIR'; import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads'; import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies'; @@ -44,7 +46,12 @@ import { DependencyCollectionContext, handleInstruction, } from '../HIR/PropagateScopeDependenciesHIR'; -import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {buildDependencyInstructions} from '../HIR/ScopeDependencyUtils'; +import { + eachInstructionOperand, + eachTerminalOperand, + terminalFallthrough, +} from '../HIR/visitors'; import {empty} from '../Utils/Stack'; import {getOrInsertWith} from '../Utils/utils'; @@ -53,7 +60,6 @@ import {getOrInsertWith} from '../Utils/utils'; * a second argument to the useEffect call if no dependency array is provided. */ export function inferEffectDependencies(fn: HIRFunction): void { - let hasRewrite = false; const fnExpressions = new Map< IdentifierId, TInstruction @@ -86,6 +92,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) */ const reactiveIds = inferReactiveIdentifiers(fn); + const rewriteBlocks: Array = []; for (const [, block] of fn.body.blocks) { if (block.terminal.kind === 'scope') { @@ -101,7 +108,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { ); } } - const rewriteInstrs = new Map>(); + const rewriteInstrs: Array = []; for (const instr of block.instructions) { const {value, lvalue} = instr; if (value.kind === 'FunctionExpression') { @@ -165,7 +172,6 @@ export function inferEffectDependencies(fn: HIRFunction): void { ) { // We have a useEffect call with no deps array, so we need to infer the deps const effectDeps: Array = []; - const newInstructions: Array = []; const deps: ArrayExpression = { kind: 'ArrayExpression', elements: effectDeps, @@ -196,24 +202,28 @@ export function inferEffectDependencies(fn: HIRFunction): void { */ const usedDeps = []; - for (const dep of minimalDeps) { + for (const maybeDep of minimalDeps) { if ( - ((isUseRefType(dep.identifier) || - isSetStateType(dep.identifier)) && - !reactiveIds.has(dep.identifier.id)) || - isFireFunctionType(dep.identifier) + ((isUseRefType(maybeDep.identifier) || + isSetStateType(maybeDep.identifier)) && + !reactiveIds.has(maybeDep.identifier.id)) || + isFireFunctionType(maybeDep.identifier) ) { // exclude non-reactive hook results, which will never be in a memo block continue; } - const {place, instructions} = writeDependencyToInstructions( + const dep = truncateDepAtCurrent(maybeDep); + const {place, value, exitBlockId} = buildDependencyInstructions( dep, - reactiveIds.has(dep.identifier.id), fn.env, - fnExpr.loc, ); - newInstructions.push(...instructions); + rewriteInstrs.push({ + kind: 'block', + location: instr.id, + value, + exitBlockId: exitBlockId, + }); effectDeps.push(place); usedDeps.push(dep); } @@ -234,27 +244,32 @@ export function inferEffectDependencies(fn: HIRFunction): void { }); } - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, - }); - // Step 2: push the inferred deps array as an argument of the useEffect + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, + }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } else if (loadGlobals.has(value.args[0].identifier.id)) { // Global functions have no reactive dependencies, so we can insert an empty array - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } } else if ( @@ -285,21 +300,13 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } } - if (rewriteInstrs.size > 0) { - hasRewrite = true; - const newInstrs = []; - for (const instr of block.instructions) { - const newInstr = rewriteInstrs.get(instr.id); - if (newInstr != null) { - newInstrs.push(...newInstr, instr); - } else { - newInstrs.push(instr); - } - } - block.instructions = newInstrs; - } + rewriteSplices(block, rewriteInstrs, rewriteBlocks); } - if (hasRewrite) { + + if (rewriteBlocks.length > 0) { + for (const block of rewriteBlocks) { + fn.body.blocks.set(block.id, block); + } // Renumber instructions and fix scope ranges markInstructionIds(fn.body); fixScopeAndIdentifierRanges(fn.body); @@ -307,63 +314,144 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } -function writeDependencyToInstructions( +function truncateDepAtCurrent( dep: ReactiveScopeDependency, - reactive: boolean, - env: Environment, - loc: SourceLocation, -): {place: Place; instructions: Array} { - const instructions: Array = []; - let currValue = createTemporaryPlace(env, GeneratedSource); - currValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...currValue, effect: Effect.Mutate}, - value: { - kind: 'LoadLocal', - place: { - kind: 'Identifier', - identifier: dep.identifier, - effect: Effect.Capture, - reactive, - loc: loc, - }, - loc: loc, - }, - }); - for (const path of dep.path) { - if (path.optional) { - /** - * TODO: instead of truncating optional paths, reuse - * instructions from hoisted dependencies block(s) - */ - break; +): ReactiveScopeDependency { + const idx = dep.path.findIndex(path => path.property === 'current'); + if (idx === -1) { + return dep; + } else { + return {...dep, path: dep.path.slice(0, idx)}; + } +} + +type SpliceInfo = + | {kind: 'instr'; location: InstructionId; value: Instruction} + | { + kind: 'block'; + location: InstructionId; + value: HIR; + exitBlockId: BlockId; + }; + +function rewriteSplices( + originalBlock: BasicBlock, + splices: Array, + rewriteBlocks: Array, +): void { + if (splices.length === 0) { + return; + } + /** + * Splice instructions or value blocks into the original block. + * --- original block --- + * bb_original + * instr1 + * ... + * instr2 <-- splice location + * instr3 + * ... + * + * + * If there is more than one block in the splice, this means that we're + * splicing in a set of value-blocks of the following structure: + * --- blocks we're splicing in --- + * bb_entry: + * instrEntry + * ... + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... + * + * + * + * --- rewritten blocks --- + * bb_original + * instr1 + * ... (original instructions) + * instr2 + * instrEntry + * ... (spliced instructions) + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... (spliced instructions) + * instr3 + * ... (original instructions) + * + */ + const originalInstrs = originalBlock.instructions; + let currBlock: BasicBlock = {...originalBlock, instructions: []}; + rewriteBlocks.push(currBlock); + + let cursor = 0; + for (const rewrite of splices) { + while (originalInstrs[cursor].id < rewrite.location) { + CompilerError.invariant( + originalInstrs[cursor].id < originalInstrs[cursor + 1].id, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted', + loc: originalInstrs[cursor].loc, + }, + ); + currBlock.instructions.push(originalInstrs[cursor]); + cursor++; } - if (path.property === 'current') { - /* - * Prune ref.current accesses. This may over-capture for non-ref values with - * a current property, but that's fine. - */ - break; + CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, { + reason: + '[InferEffectDependencies] Internal invariant broken: splice location not found', + loc: originalInstrs[cursor].loc, + }); + + if (rewrite.kind === 'instr') { + currBlock.instructions.push(rewrite.value); + } else { + const {entry, blocks} = rewrite.value; + const entryBlock = blocks.get(entry)!; + // splice in all instructions from the entry block + currBlock.instructions.push(...entryBlock.instructions); + if (blocks.size > 1) { + /** + * We're splicing in a set of value-blocks, which means we need + * to push new blocks and update terminals. + */ + CompilerError.invariant( + terminalFallthrough(entryBlock.terminal) === rewrite.exitBlockId, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough', + loc: entryBlock.terminal.loc, + }, + ); + const originalTerminal = currBlock.terminal; + currBlock.terminal = entryBlock.terminal; + + for (const [id, block] of blocks) { + if (id === entry) { + continue; + } + if (id === rewrite.exitBlockId) { + block.terminal = originalTerminal; + currBlock = block; + } + rewriteBlocks.push(block); + } + } } - const nextValue = createTemporaryPlace(env, GeneratedSource); - nextValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...nextValue, effect: Effect.Mutate}, - value: { - kind: 'PropertyLoad', - object: {...currValue, effect: Effect.Capture}, - property: path.property, - loc: loc, - }, - }); - currValue = nextValue; + } + for (let i = cursor; i < originalInstrs.length; i++) { + currBlock.instructions.push(originalInstrs[i]); } - currValue.effect = Effect.Freeze; - return {place: currValue, instructions}; } function inferReactiveIdentifiers(fn: HIRFunction): Set { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md new file mode 100644 index 0000000000..e4560848dd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +function Component(t0) { + const { foo } = t0; + const arr = []; + + useEffect(() => print(arr[0]?.value), [arr[0]?.value]); + arr.push({ value: foo }); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":304},"end":{"line":9,"column":39,"index":341},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":326},"end":{"line":9,"column":27,"index":329},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) [{"value":1}] +logs: [1] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js new file mode 100644 index 0000000000..c435b72d1a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js @@ -0,0 +1,17 @@ +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index 05ef40c150..5e6f19dd83 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -14,12 +14,17 @@ function Component({arrRef}) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect, useRef } from "react"; import { print } from "shared-runtime"; @@ -32,7 +37,21 @@ function Component(t0) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ arrRef: { current: { val: "initial ref value" } } }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":227},"end":{"line":8,"column":40,"index":265},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":249},"end":{"line":8,"column":30,"index":255},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) {"current":{"val":2}} +logs: [{ val: 2 }] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js index f497d7e595..bd3f6d1de5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -9,3 +9,8 @@ function Component({arrRef}) { arrRef.current.val = 2; return arrRef; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index fa1df3ef88..3b61fbf834 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -2,33 +2,55 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect } from "react"; function Component(t0) { const { foo } = t0; const arr = []; - useEffect(() => arr.push(foo), [arr, foo]); + useEffect(() => { + arr.push(foo); + }, [arr, foo]); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":149},"end":{"line":8,"column":4,"index":190},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":180},"end":{"line":7,"column":16,"index":183},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) [2] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js index 2e2eb7bc08..fbcbf004a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js @@ -1,9 +1,16 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md new file mode 100644 index 0000000000..be107a2afa --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md @@ -0,0 +1,101 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print, shallowCopy } from "shared-runtime"; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr(t0) { + const $ = _c(13); + const { cond, propVal } = t0; + let t1; + if ($[0] !== cond || $[1] !== propVal) { + t1 = cond ? { b: propVal } : null; + $[0] = cond; + $[1] = propVal; + $[2] = t1; + } else { + t1 = $[2]; + } + let t2; + if ($[3] !== t1) { + t2 = { a: t1, c: null }; + $[3] = t1; + $[4] = t2; + } else { + t2 = $[4]; + } + const obj = t2; + const t3 = propVal + 1; + let t4; + if ($[5] !== t3) { + t4 = shallowCopy({ a: { b: { c: { d: { e: { f: t3 } } } } } }); + $[5] = t3; + $[6] = t4; + } else { + t4 = $[6]; + } + const other = t4; + let t5; + if ($[7] !== propVal) { + t5 = shallowCopy(propVal); + $[7] = propVal; + $[8] = t5; + } else { + t5 = $[8]; + } + const primitive = t5; + let t6; + if ( + $[9] !== obj.a?.b || + $[10] !== other?.a?.b?.c?.d?.e.f || + $[11] !== primitive.a?.b.c?.d?.e.f + ) { + t6 = () => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f); + $[9] = obj.a?.b; + $[10] = other?.a?.b?.c?.d?.e.f; + $[11] = primitive.a?.b.c?.d?.e.f; + $[12] = t6; + } else { + t6 = $[12]; + } + useEffect(t6, [obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + +``` + +### Eval output +(kind: ok) +logs: [1,2,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js new file mode 100644 index 0000000000..f6e5a18d47 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js @@ -0,0 +1,18 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md index 7c9f21b85c..d2414f3f9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md @@ -8,10 +8,16 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + ``` ## Code @@ -23,7 +29,7 @@ import { print } from "shared-runtime"; // TODO: take optional chains as dependencies function ReactiveMemberExpr(t0) { - const $ = _c(7); + const $ = _c(9); const { cond, propVal } = t0; let t1; if ($[0] !== cond || $[1] !== propVal) { @@ -36,7 +42,7 @@ function ReactiveMemberExpr(t0) { } let t2; if ($[3] !== t1) { - t2 = { a: t1 }; + t2 = { a: t1, c: null }; $[3] = t1; $[4] = t2; } else { @@ -51,10 +57,25 @@ function ReactiveMemberExpr(t0) { } else { t3 = $[6]; } - useEffect(t3, [obj.a]); + useEffect(t3, [obj.a?.b]); + let t4; + if ($[7] !== obj.c?.d) { + t4 = () => print(obj.c?.d); + $[7] = obj.c?.d; + $[8] = t4; + } else { + t4 = $[8]; + } + useEffect(t4, [obj.c?.d]); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) +logs: [1,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js index 8a76784e24..4f567fa152 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js @@ -4,6 +4,12 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; From 0dfaec836ec9c9c7a7e89e1b676a1af0c39eaf98 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 15:19:50 -0400 Subject: [PATCH 407/422] [compiler] Inferred effect dependencies now include optional chains Inferred effect dependencies now include optional chains. This is a temporary solution while https://github.com/facebook/react/pull/32099 and its followups are worked on. Ideally, we should model reactive scope dependencies in the IR similarly to `ComputeIR` -- dependencies should be hoisted and all references rewritten to use the hoisted dependencies. ` --- .../src/HIR/ScopeDependencyUtils.ts | 283 +++++++++++++++++ .../src/Inference/InferEffectDependencies.ts | 288 ++++++++++++------ ...e-after-useeffect-optional-chain.expect.md | 58 ++++ .../mutate-after-useeffect-optional-chain.js | 17 ++ ...utate-after-useeffect-ref-access.expect.md | 25 +- .../mutate-after-useeffect-ref-access.js | 7 +- .../mutate-after-useeffect.expect.md | 32 +- .../bailout-retry/mutate-after-useeffect.js | 11 +- .../reactive-optional-chain-complex.expect.md | 101 ++++++ .../reactive-optional-chain-complex.js | 18 ++ .../reactive-optional-chain.expect.md | 31 +- .../reactive-optional-chain.js | 8 +- 12 files changed, 767 insertions(+), 112 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts new file mode 100644 index 0000000000..5d30aeb644 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts @@ -0,0 +1,283 @@ +import { + Place, + ReactiveScopeDependency, + Identifier, + makeInstructionId, + InstructionKind, + GeneratedSource, + BlockId, + makeTemporaryIdentifier, + Effect, + GotoVariant, + HIR, +} from './HIR'; +import {CompilerError} from '../CompilerError'; +import {Environment} from './Environment'; +import HIRBuilder from './HIRBuilder'; +import {lowerValueToTemporary} from './BuildHIR'; + +type DependencyInstructions = { + place: Place; + value: HIR; + exitBlockId: BlockId; +}; + +export function buildDependencyInstructions( + dep: ReactiveScopeDependency, + env: Environment, +): DependencyInstructions { + const builder = new HIRBuilder(env, { + entryBlockKind: 'value', + }); + let dependencyValue: Identifier; + if (dep.path.every(path => !path.optional)) { + dependencyValue = writeNonOptionalDependency(dep, env, builder); + } else { + dependencyValue = writeOptionalDependency(dep, builder, null); + } + + const exitBlockId = builder.terminate( + { + kind: 'unsupported', + loc: GeneratedSource, + id: makeInstructionId(0), + }, + null, + ); + return { + place: { + kind: 'Identifier', + identifier: dependencyValue, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + value: builder.build(), + exitBlockId, + }; +} + +/** + * Write instructions for a simple dependency (without optional chains) + */ +function writeNonOptionalDependency( + dep: ReactiveScopeDependency, + env: Environment, + builder: HIRBuilder, +): Identifier { + const loc = dep.identifier.loc; + let curr: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: curr, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'LoadLocal', + place: { + identifier: dep.identifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + + /** + * Iteratively build up dependency instructions by reading from the last written + * instruction. + */ + for (const path of dep.path) { + const next = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: next, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'PropertyLoad', + object: { + identifier: curr, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + property: path.property, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + curr = next; + } + return curr; +} + +/** + * Write a dependency into optional blocks. + * + * e.g. `a.b?.c.d` is written to an optional block that tests `a.b` and + * conditionally evaluates `c.d`. + */ +function writeOptionalDependency( + dep: ReactiveScopeDependency, + builder: HIRBuilder, + parentAlternate: BlockId | null, +): Identifier { + const env = builder.environment; + /** + * Reserve an identifier which will be used to store the result of this + * dependency. + */ + const dependencyValue: Place = { + kind: 'Identifier', + identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource), + effect: Effect.Mutate, + reactive: dep.reactive, + loc: GeneratedSource, + }; + + /** + * Reserve a block which is the fallthrough (and transitive successor) of this + * optional chain. + */ + const continuationBlock = builder.reserve(builder.currentBlockKind()); + let alternate; + if (parentAlternate != null) { + alternate = parentAlternate; + } else { + /** + * If an outermost alternate block has not been reserved, write one + * + * $N = Primitive undefined + * $M = StoreLocal $OptionalResult = $N + * goto fallthrough + */ + alternate = builder.enter('value', () => { + const temp = lowerValueToTemporary(builder, { + kind: 'Primitive', + value: undefined, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}}, + value: {...temp}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + } + + // Reserve the consequent block, which is the successor of the test block + const consequent = builder.reserve('value'); + + let testIdentifier: Identifier | null = null; + const testBlock = builder.enter('value', () => { + const testDependency = { + ...dep, + path: dep.path.slice(0, dep.path.length - 1), + }; + const firstOptional = dep.path.findIndex(path => path.optional); + CompilerError.invariant(firstOptional !== -1, { + reason: + '[ScopeDependencyUtils] Internal invariant broken: expected optional path', + loc: dep.identifier.loc, + description: null, + suggestions: null, + }); + if (firstOptional === dep.path.length - 1) { + // Base case: the test block is simple + testIdentifier = writeNonOptionalDependency(testDependency, env, builder); + } else { + // Otherwise, the test block is a nested optional chain + testIdentifier = writeOptionalDependency( + testDependency, + builder, + alternate, + ); + } + + return { + kind: 'branch', + test: { + identifier: testIdentifier, + effect: Effect.Freeze, + kind: 'Identifier', + loc: GeneratedSource, + reactive: dep.reactive, + }, + consequent: consequent.id, + alternate, + id: makeInstructionId(0), + loc: GeneratedSource, + fallthrough: continuationBlock.id, + }; + }); + + builder.enterReserved(consequent, () => { + CompilerError.invariant(testIdentifier !== null, { + reason: 'Satisfy type checker', + description: null, + loc: null, + suggestions: null, + }); + + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}}, + value: lowerValueToTemporary(builder, { + kind: 'PropertyLoad', + object: { + identifier: testIdentifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + property: dep.path.at(-1)!.property, + loc: GeneratedSource, + }), + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + builder.terminateWithContinuation( + { + kind: 'optional', + optional: dep.path.at(-1)!.optional, + test: testBlock, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }, + continuationBlock, + ); + + return dependencyValue.identifier; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index f1a5843419..efbf8d8dd8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -10,7 +10,6 @@ import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, Effect, - Environment, FunctionExpression, GeneratedSource, HIRFunction, @@ -29,6 +28,9 @@ import { isSetStateType, isFireFunctionType, makeScopeId, + HIR, + BasicBlock, + BlockId, } from '../HIR'; import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads'; import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies'; @@ -38,13 +40,20 @@ import { createTemporaryPlace, fixScopeAndIdentifierRanges, markInstructionIds, + markPredecessors, + reversePostorderBlocks, } from '../HIR/HIRBuilder'; import { collectTemporariesSidemap, DependencyCollectionContext, handleInstruction, } from '../HIR/PropagateScopeDependenciesHIR'; -import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {buildDependencyInstructions} from '../HIR/ScopeDependencyUtils'; +import { + eachInstructionOperand, + eachTerminalOperand, + terminalFallthrough, +} from '../HIR/visitors'; import {empty} from '../Utils/Stack'; import {getOrInsertWith} from '../Utils/utils'; @@ -53,7 +62,6 @@ import {getOrInsertWith} from '../Utils/utils'; * a second argument to the useEffect call if no dependency array is provided. */ export function inferEffectDependencies(fn: HIRFunction): void { - let hasRewrite = false; const fnExpressions = new Map< IdentifierId, TInstruction @@ -86,6 +94,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) */ const reactiveIds = inferReactiveIdentifiers(fn); + const rewriteBlocks: Array = []; for (const [, block] of fn.body.blocks) { if (block.terminal.kind === 'scope') { @@ -101,7 +110,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { ); } } - const rewriteInstrs = new Map>(); + const rewriteInstrs: Array = []; for (const instr of block.instructions) { const {value, lvalue} = instr; if (value.kind === 'FunctionExpression') { @@ -165,7 +174,6 @@ export function inferEffectDependencies(fn: HIRFunction): void { ) { // We have a useEffect call with no deps array, so we need to infer the deps const effectDeps: Array = []; - const newInstructions: Array = []; const deps: ArrayExpression = { kind: 'ArrayExpression', elements: effectDeps, @@ -196,24 +204,28 @@ export function inferEffectDependencies(fn: HIRFunction): void { */ const usedDeps = []; - for (const dep of minimalDeps) { + for (const maybeDep of minimalDeps) { if ( - ((isUseRefType(dep.identifier) || - isSetStateType(dep.identifier)) && - !reactiveIds.has(dep.identifier.id)) || - isFireFunctionType(dep.identifier) + ((isUseRefType(maybeDep.identifier) || + isSetStateType(maybeDep.identifier)) && + !reactiveIds.has(maybeDep.identifier.id)) || + isFireFunctionType(maybeDep.identifier) ) { // exclude non-reactive hook results, which will never be in a memo block continue; } - const {place, instructions} = writeDependencyToInstructions( + const dep = truncateDepAtCurrent(maybeDep); + const {place, value, exitBlockId} = buildDependencyInstructions( dep, - reactiveIds.has(dep.identifier.id), fn.env, - fnExpr.loc, ); - newInstructions.push(...instructions); + rewriteInstrs.push({ + kind: 'block', + location: instr.id, + value, + exitBlockId: exitBlockId, + }); effectDeps.push(place); usedDeps.push(dep); } @@ -234,27 +246,32 @@ export function inferEffectDependencies(fn: HIRFunction): void { }); } - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, - }); - // Step 2: push the inferred deps array as an argument of the useEffect + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, + }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } else if (loadGlobals.has(value.args[0].identifier.id)) { // Global functions have no reactive dependencies, so we can insert an empty array - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } } else if ( @@ -285,85 +302,166 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } } - if (rewriteInstrs.size > 0) { - hasRewrite = true; - const newInstrs = []; - for (const instr of block.instructions) { - const newInstr = rewriteInstrs.get(instr.id); - if (newInstr != null) { - newInstrs.push(...newInstr, instr); - } else { - newInstrs.push(instr); - } - } - block.instructions = newInstrs; - } + rewriteSplices(block, rewriteInstrs, rewriteBlocks); } - if (hasRewrite) { + + if (rewriteBlocks.length > 0) { + for (const block of rewriteBlocks) { + fn.body.blocks.set(block.id, block); + } + + /** + * Fixup the HIR to restore RPO, ensure correct predecessors, and renumber + * instructions. + */ + reversePostorderBlocks(fn.body); + markPredecessors(fn.body); // Renumber instructions and fix scope ranges markInstructionIds(fn.body); fixScopeAndIdentifierRanges(fn.body); + fn.env.hasInferredEffect = true; } } -function writeDependencyToInstructions( +function truncateDepAtCurrent( dep: ReactiveScopeDependency, - reactive: boolean, - env: Environment, - loc: SourceLocation, -): {place: Place; instructions: Array} { - const instructions: Array = []; - let currValue = createTemporaryPlace(env, GeneratedSource); - currValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...currValue, effect: Effect.Mutate}, - value: { - kind: 'LoadLocal', - place: { - kind: 'Identifier', - identifier: dep.identifier, - effect: Effect.Capture, - reactive, - loc: loc, - }, - loc: loc, - }, - }); - for (const path of dep.path) { - if (path.optional) { - /** - * TODO: instead of truncating optional paths, reuse - * instructions from hoisted dependencies block(s) - */ - break; +): ReactiveScopeDependency { + const idx = dep.path.findIndex(path => path.property === 'current'); + if (idx === -1) { + return dep; + } else { + return {...dep, path: dep.path.slice(0, idx)}; + } +} + +type SpliceInfo = + | {kind: 'instr'; location: InstructionId; value: Instruction} + | { + kind: 'block'; + location: InstructionId; + value: HIR; + exitBlockId: BlockId; + }; + +function rewriteSplices( + originalBlock: BasicBlock, + splices: Array, + rewriteBlocks: Array, +): void { + if (splices.length === 0) { + return; + } + /** + * Splice instructions or value blocks into the original block. + * --- original block --- + * bb_original + * instr1 + * ... + * instr2 <-- splice location + * instr3 + * ... + * + * + * If there is more than one block in the splice, this means that we're + * splicing in a set of value-blocks of the following structure: + * --- blocks we're splicing in --- + * bb_entry: + * instrEntry + * ... + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... + * + * + * + * --- rewritten blocks --- + * bb_original + * instr1 + * ... (original instructions) + * instr2 + * instrEntry + * ... (spliced instructions) + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... (spliced instructions) + * instr3 + * ... (original instructions) + * + */ + const originalInstrs = originalBlock.instructions; + let currBlock: BasicBlock = {...originalBlock, instructions: []}; + rewriteBlocks.push(currBlock); + + let cursor = 0; + for (const rewrite of splices) { + while (originalInstrs[cursor].id < rewrite.location) { + CompilerError.invariant( + originalInstrs[cursor].id < originalInstrs[cursor + 1].id, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted', + loc: originalInstrs[cursor].loc, + }, + ); + currBlock.instructions.push(originalInstrs[cursor]); + cursor++; } - if (path.property === 'current') { - /* - * Prune ref.current accesses. This may over-capture for non-ref values with - * a current property, but that's fine. - */ - break; + CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, { + reason: + '[InferEffectDependencies] Internal invariant broken: splice location not found', + loc: originalInstrs[cursor].loc, + }); + + if (rewrite.kind === 'instr') { + currBlock.instructions.push(rewrite.value); + } else { + const {entry, blocks} = rewrite.value; + const entryBlock = blocks.get(entry)!; + // splice in all instructions from the entry block + currBlock.instructions.push(...entryBlock.instructions); + if (blocks.size > 1) { + /** + * We're splicing in a set of value-blocks, which means we need + * to push new blocks and update terminals. + */ + CompilerError.invariant( + terminalFallthrough(entryBlock.terminal) === rewrite.exitBlockId, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough', + loc: entryBlock.terminal.loc, + }, + ); + const originalTerminal = currBlock.terminal; + currBlock.terminal = entryBlock.terminal; + + for (const [id, block] of blocks) { + if (id === entry) { + continue; + } + if (id === rewrite.exitBlockId) { + block.terminal = originalTerminal; + currBlock = block; + } + rewriteBlocks.push(block); + } + } } - const nextValue = createTemporaryPlace(env, GeneratedSource); - nextValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...nextValue, effect: Effect.Mutate}, - value: { - kind: 'PropertyLoad', - object: {...currValue, effect: Effect.Capture}, - property: path.property, - loc: loc, - }, - }); - currValue = nextValue; + } + for (let i = cursor; i < originalInstrs.length; i++) { + currBlock.instructions.push(originalInstrs[i]); } - currValue.effect = Effect.Freeze; - return {place: currValue, instructions}; } function inferReactiveIdentifiers(fn: HIRFunction): Set { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md new file mode 100644 index 0000000000..e4560848dd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +function Component(t0) { + const { foo } = t0; + const arr = []; + + useEffect(() => print(arr[0]?.value), [arr[0]?.value]); + arr.push({ value: foo }); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":304},"end":{"line":9,"column":39,"index":341},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":326},"end":{"line":9,"column":27,"index":329},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) [{"value":1}] +logs: [1] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js new file mode 100644 index 0000000000..c435b72d1a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js @@ -0,0 +1,17 @@ +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index 05ef40c150..5e6f19dd83 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -14,12 +14,17 @@ function Component({arrRef}) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect, useRef } from "react"; import { print } from "shared-runtime"; @@ -32,7 +37,21 @@ function Component(t0) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ arrRef: { current: { val: "initial ref value" } } }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":227},"end":{"line":8,"column":40,"index":265},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":249},"end":{"line":8,"column":30,"index":255},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) {"current":{"val":2}} +logs: [{ val: 2 }] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js index f497d7e595..bd3f6d1de5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -9,3 +9,8 @@ function Component({arrRef}) { arrRef.current.val = 2; return arrRef; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index fa1df3ef88..3b61fbf834 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -2,33 +2,55 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect } from "react"; function Component(t0) { const { foo } = t0; const arr = []; - useEffect(() => arr.push(foo), [arr, foo]); + useEffect(() => { + arr.push(foo); + }, [arr, foo]); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":149},"end":{"line":8,"column":4,"index":190},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":180},"end":{"line":7,"column":16,"index":183},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) [2] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js index 2e2eb7bc08..fbcbf004a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js @@ -1,9 +1,16 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md new file mode 100644 index 0000000000..be107a2afa --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md @@ -0,0 +1,101 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print, shallowCopy } from "shared-runtime"; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr(t0) { + const $ = _c(13); + const { cond, propVal } = t0; + let t1; + if ($[0] !== cond || $[1] !== propVal) { + t1 = cond ? { b: propVal } : null; + $[0] = cond; + $[1] = propVal; + $[2] = t1; + } else { + t1 = $[2]; + } + let t2; + if ($[3] !== t1) { + t2 = { a: t1, c: null }; + $[3] = t1; + $[4] = t2; + } else { + t2 = $[4]; + } + const obj = t2; + const t3 = propVal + 1; + let t4; + if ($[5] !== t3) { + t4 = shallowCopy({ a: { b: { c: { d: { e: { f: t3 } } } } } }); + $[5] = t3; + $[6] = t4; + } else { + t4 = $[6]; + } + const other = t4; + let t5; + if ($[7] !== propVal) { + t5 = shallowCopy(propVal); + $[7] = propVal; + $[8] = t5; + } else { + t5 = $[8]; + } + const primitive = t5; + let t6; + if ( + $[9] !== obj.a?.b || + $[10] !== other?.a?.b?.c?.d?.e.f || + $[11] !== primitive.a?.b.c?.d?.e.f + ) { + t6 = () => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f); + $[9] = obj.a?.b; + $[10] = other?.a?.b?.c?.d?.e.f; + $[11] = primitive.a?.b.c?.d?.e.f; + $[12] = t6; + } else { + t6 = $[12]; + } + useEffect(t6, [obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + +``` + +### Eval output +(kind: ok) +logs: [1,2,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js new file mode 100644 index 0000000000..f6e5a18d47 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js @@ -0,0 +1,18 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md index 7c9f21b85c..d2414f3f9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md @@ -8,10 +8,16 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + ``` ## Code @@ -23,7 +29,7 @@ import { print } from "shared-runtime"; // TODO: take optional chains as dependencies function ReactiveMemberExpr(t0) { - const $ = _c(7); + const $ = _c(9); const { cond, propVal } = t0; let t1; if ($[0] !== cond || $[1] !== propVal) { @@ -36,7 +42,7 @@ function ReactiveMemberExpr(t0) { } let t2; if ($[3] !== t1) { - t2 = { a: t1 }; + t2 = { a: t1, c: null }; $[3] = t1; $[4] = t2; } else { @@ -51,10 +57,25 @@ function ReactiveMemberExpr(t0) { } else { t3 = $[6]; } - useEffect(t3, [obj.a]); + useEffect(t3, [obj.a?.b]); + let t4; + if ($[7] !== obj.c?.d) { + t4 = () => print(obj.c?.d); + $[7] = obj.c?.d; + $[8] = t4; + } else { + t4 = $[8]; + } + useEffect(t4, [obj.c?.d]); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) +logs: [1,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js index 8a76784e24..4f567fa152 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js @@ -4,6 +4,12 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; From 1a5a050900f15e84b5f96541fc9afba3e7bcc038 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 15:19:50 -0400 Subject: [PATCH 408/422] [compiler] Inferred effect dependencies now include optional chains Inferred effect dependencies now include optional chains. This is a temporary solution while https://github.com/facebook/react/pull/32099 and its followups are worked on. Ideally, we should model reactive scope dependencies in the IR similarly to `ComputeIR` -- dependencies should be hoisted and all references rewritten to use the hoisted dependencies. ` --- .../src/HIR/ScopeDependencyUtils.ts | 283 +++++++++++++++++ .../src/Inference/InferEffectDependencies.ts | 290 ++++++++++++------ ...e-after-useeffect-optional-chain.expect.md | 58 ++++ .../mutate-after-useeffect-optional-chain.js | 17 + ...utate-after-useeffect-ref-access.expect.md | 25 +- .../mutate-after-useeffect-ref-access.js | 7 +- .../mutate-after-useeffect.expect.md | 32 +- .../bailout-retry/mutate-after-useeffect.js | 11 +- .../reactive-optional-chain-complex.expect.md | 101 ++++++ .../reactive-optional-chain-complex.js | 18 ++ .../reactive-optional-chain.expect.md | 31 +- .../reactive-optional-chain.js | 8 +- 12 files changed, 767 insertions(+), 114 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts new file mode 100644 index 0000000000..5d30aeb644 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts @@ -0,0 +1,283 @@ +import { + Place, + ReactiveScopeDependency, + Identifier, + makeInstructionId, + InstructionKind, + GeneratedSource, + BlockId, + makeTemporaryIdentifier, + Effect, + GotoVariant, + HIR, +} from './HIR'; +import {CompilerError} from '../CompilerError'; +import {Environment} from './Environment'; +import HIRBuilder from './HIRBuilder'; +import {lowerValueToTemporary} from './BuildHIR'; + +type DependencyInstructions = { + place: Place; + value: HIR; + exitBlockId: BlockId; +}; + +export function buildDependencyInstructions( + dep: ReactiveScopeDependency, + env: Environment, +): DependencyInstructions { + const builder = new HIRBuilder(env, { + entryBlockKind: 'value', + }); + let dependencyValue: Identifier; + if (dep.path.every(path => !path.optional)) { + dependencyValue = writeNonOptionalDependency(dep, env, builder); + } else { + dependencyValue = writeOptionalDependency(dep, builder, null); + } + + const exitBlockId = builder.terminate( + { + kind: 'unsupported', + loc: GeneratedSource, + id: makeInstructionId(0), + }, + null, + ); + return { + place: { + kind: 'Identifier', + identifier: dependencyValue, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + value: builder.build(), + exitBlockId, + }; +} + +/** + * Write instructions for a simple dependency (without optional chains) + */ +function writeNonOptionalDependency( + dep: ReactiveScopeDependency, + env: Environment, + builder: HIRBuilder, +): Identifier { + const loc = dep.identifier.loc; + let curr: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: curr, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'LoadLocal', + place: { + identifier: dep.identifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + + /** + * Iteratively build up dependency instructions by reading from the last written + * instruction. + */ + for (const path of dep.path) { + const next = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: next, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'PropertyLoad', + object: { + identifier: curr, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + property: path.property, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + curr = next; + } + return curr; +} + +/** + * Write a dependency into optional blocks. + * + * e.g. `a.b?.c.d` is written to an optional block that tests `a.b` and + * conditionally evaluates `c.d`. + */ +function writeOptionalDependency( + dep: ReactiveScopeDependency, + builder: HIRBuilder, + parentAlternate: BlockId | null, +): Identifier { + const env = builder.environment; + /** + * Reserve an identifier which will be used to store the result of this + * dependency. + */ + const dependencyValue: Place = { + kind: 'Identifier', + identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource), + effect: Effect.Mutate, + reactive: dep.reactive, + loc: GeneratedSource, + }; + + /** + * Reserve a block which is the fallthrough (and transitive successor) of this + * optional chain. + */ + const continuationBlock = builder.reserve(builder.currentBlockKind()); + let alternate; + if (parentAlternate != null) { + alternate = parentAlternate; + } else { + /** + * If an outermost alternate block has not been reserved, write one + * + * $N = Primitive undefined + * $M = StoreLocal $OptionalResult = $N + * goto fallthrough + */ + alternate = builder.enter('value', () => { + const temp = lowerValueToTemporary(builder, { + kind: 'Primitive', + value: undefined, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}}, + value: {...temp}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + } + + // Reserve the consequent block, which is the successor of the test block + const consequent = builder.reserve('value'); + + let testIdentifier: Identifier | null = null; + const testBlock = builder.enter('value', () => { + const testDependency = { + ...dep, + path: dep.path.slice(0, dep.path.length - 1), + }; + const firstOptional = dep.path.findIndex(path => path.optional); + CompilerError.invariant(firstOptional !== -1, { + reason: + '[ScopeDependencyUtils] Internal invariant broken: expected optional path', + loc: dep.identifier.loc, + description: null, + suggestions: null, + }); + if (firstOptional === dep.path.length - 1) { + // Base case: the test block is simple + testIdentifier = writeNonOptionalDependency(testDependency, env, builder); + } else { + // Otherwise, the test block is a nested optional chain + testIdentifier = writeOptionalDependency( + testDependency, + builder, + alternate, + ); + } + + return { + kind: 'branch', + test: { + identifier: testIdentifier, + effect: Effect.Freeze, + kind: 'Identifier', + loc: GeneratedSource, + reactive: dep.reactive, + }, + consequent: consequent.id, + alternate, + id: makeInstructionId(0), + loc: GeneratedSource, + fallthrough: continuationBlock.id, + }; + }); + + builder.enterReserved(consequent, () => { + CompilerError.invariant(testIdentifier !== null, { + reason: 'Satisfy type checker', + description: null, + loc: null, + suggestions: null, + }); + + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}}, + value: lowerValueToTemporary(builder, { + kind: 'PropertyLoad', + object: { + identifier: testIdentifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + property: dep.path.at(-1)!.property, + loc: GeneratedSource, + }), + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + builder.terminateWithContinuation( + { + kind: 'optional', + optional: dep.path.at(-1)!.optional, + test: testBlock, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }, + continuationBlock, + ); + + return dependencyValue.identifier; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index f1a5843419..4daa2f9fba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -10,7 +10,6 @@ import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, Effect, - Environment, FunctionExpression, GeneratedSource, HIRFunction, @@ -29,6 +28,9 @@ import { isSetStateType, isFireFunctionType, makeScopeId, + HIR, + BasicBlock, + BlockId, } from '../HIR'; import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads'; import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies'; @@ -38,13 +40,20 @@ import { createTemporaryPlace, fixScopeAndIdentifierRanges, markInstructionIds, + markPredecessors, + reversePostorderBlocks, } from '../HIR/HIRBuilder'; import { collectTemporariesSidemap, DependencyCollectionContext, handleInstruction, } from '../HIR/PropagateScopeDependenciesHIR'; -import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {buildDependencyInstructions} from '../HIR/ScopeDependencyUtils'; +import { + eachInstructionOperand, + eachTerminalOperand, + terminalFallthrough, +} from '../HIR/visitors'; import {empty} from '../Utils/Stack'; import {getOrInsertWith} from '../Utils/utils'; @@ -53,7 +62,6 @@ import {getOrInsertWith} from '../Utils/utils'; * a second argument to the useEffect call if no dependency array is provided. */ export function inferEffectDependencies(fn: HIRFunction): void { - let hasRewrite = false; const fnExpressions = new Map< IdentifierId, TInstruction @@ -86,6 +94,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) */ const reactiveIds = inferReactiveIdentifiers(fn); + const rewriteBlocks: Array = []; for (const [, block] of fn.body.blocks) { if (block.terminal.kind === 'scope') { @@ -101,7 +110,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { ); } } - const rewriteInstrs = new Map>(); + const rewriteInstrs: Array = []; for (const instr of block.instructions) { const {value, lvalue} = instr; if (value.kind === 'FunctionExpression') { @@ -165,7 +174,6 @@ export function inferEffectDependencies(fn: HIRFunction): void { ) { // We have a useEffect call with no deps array, so we need to infer the deps const effectDeps: Array = []; - const newInstructions: Array = []; const deps: ArrayExpression = { kind: 'ArrayExpression', elements: effectDeps, @@ -196,24 +204,28 @@ export function inferEffectDependencies(fn: HIRFunction): void { */ const usedDeps = []; - for (const dep of minimalDeps) { + for (const maybeDep of minimalDeps) { if ( - ((isUseRefType(dep.identifier) || - isSetStateType(dep.identifier)) && - !reactiveIds.has(dep.identifier.id)) || - isFireFunctionType(dep.identifier) + ((isUseRefType(maybeDep.identifier) || + isSetStateType(maybeDep.identifier)) && + !reactiveIds.has(maybeDep.identifier.id)) || + isFireFunctionType(maybeDep.identifier) ) { // exclude non-reactive hook results, which will never be in a memo block continue; } - const {place, instructions} = writeDependencyToInstructions( + const dep = truncateDepAtCurrent(maybeDep); + const {place, value, exitBlockId} = buildDependencyInstructions( dep, - reactiveIds.has(dep.identifier.id), fn.env, - fnExpr.loc, ); - newInstructions.push(...instructions); + rewriteInstrs.push({ + kind: 'block', + location: instr.id, + value, + exitBlockId: exitBlockId, + }); effectDeps.push(place); usedDeps.push(dep); } @@ -234,27 +246,32 @@ export function inferEffectDependencies(fn: HIRFunction): void { }); } - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, - }); - // Step 2: push the inferred deps array as an argument of the useEffect + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, + }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } else if (loadGlobals.has(value.args[0].identifier.id)) { // Global functions have no reactive dependencies, so we can insert an empty array - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } } else if ( @@ -285,85 +302,164 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } } - if (rewriteInstrs.size > 0) { - hasRewrite = true; - const newInstrs = []; - for (const instr of block.instructions) { - const newInstr = rewriteInstrs.get(instr.id); - if (newInstr != null) { - newInstrs.push(...newInstr, instr); - } else { - newInstrs.push(instr); - } - } - block.instructions = newInstrs; - } + rewriteSplices(block, rewriteInstrs, rewriteBlocks); } - if (hasRewrite) { + + if (rewriteBlocks.length > 0) { + for (const block of rewriteBlocks) { + fn.body.blocks.set(block.id, block); + } + + /** + * Fixup the HIR to restore RPO, ensure correct predecessors, and renumber + * instructions. + */ + reversePostorderBlocks(fn.body); + markPredecessors(fn.body); // Renumber instructions and fix scope ranges markInstructionIds(fn.body); fixScopeAndIdentifierRanges(fn.body); + fn.env.hasInferredEffect = true; } } -function writeDependencyToInstructions( +function truncateDepAtCurrent( dep: ReactiveScopeDependency, - reactive: boolean, - env: Environment, - loc: SourceLocation, -): {place: Place; instructions: Array} { - const instructions: Array = []; - let currValue = createTemporaryPlace(env, GeneratedSource); - currValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...currValue, effect: Effect.Mutate}, - value: { - kind: 'LoadLocal', - place: { - kind: 'Identifier', - identifier: dep.identifier, - effect: Effect.Capture, - reactive, - loc: loc, - }, - loc: loc, - }, - }); - for (const path of dep.path) { - if (path.optional) { - /** - * TODO: instead of truncating optional paths, reuse - * instructions from hoisted dependencies block(s) - */ - break; - } - if (path.property === 'current') { - /* - * Prune ref.current accesses. This may over-capture for non-ref values with - * a current property, but that's fine. - */ - break; - } - const nextValue = createTemporaryPlace(env, GeneratedSource); - nextValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...nextValue, effect: Effect.Mutate}, - value: { - kind: 'PropertyLoad', - object: {...currValue, effect: Effect.Capture}, - property: path.property, - loc: loc, - }, - }); - currValue = nextValue; +): ReactiveScopeDependency { + const idx = dep.path.findIndex(path => path.property === 'current'); + if (idx === -1) { + return dep; + } else { + return {...dep, path: dep.path.slice(0, idx)}; } - currValue.effect = Effect.Freeze; - return {place: currValue, instructions}; +} + +type SpliceInfo = + | {kind: 'instr'; location: InstructionId; value: Instruction} + | { + kind: 'block'; + location: InstructionId; + value: HIR; + exitBlockId: BlockId; + }; + +function rewriteSplices( + originalBlock: BasicBlock, + splices: Array, + rewriteBlocks: Array, +): void { + if (splices.length === 0) { + return; + } + /** + * Splice instructions or value blocks into the original block. + * --- original block --- + * bb_original + * instr1 + * ... + * instr2 <-- splice location + * instr3 + * ... + * + * + * If there is more than one block in the splice, this means that we're + * splicing in a set of value-blocks of the following structure: + * --- blocks we're splicing in --- + * bb_entry: + * instrEntry + * ... + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... + * + * + * + * --- rewritten blocks --- + * bb_original + * instr1 + * ... (original instructions) + * instr2 + * instrEntry + * ... (spliced instructions) + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... (spliced instructions) + * instr3 + * ... (original instructions) + * + */ + const originalInstrs = originalBlock.instructions; + let currBlock: BasicBlock = {...originalBlock, instructions: []}; + rewriteBlocks.push(currBlock); + + let cursor = 0; + for (const rewrite of splices) { + while (originalInstrs[cursor].id < rewrite.location) { + CompilerError.invariant( + originalInstrs[cursor].id < originalInstrs[cursor + 1].id, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted', + loc: originalInstrs[cursor].loc, + }, + ); + currBlock.instructions.push(originalInstrs[cursor]); + cursor++; + } + CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, { + reason: + '[InferEffectDependencies] Internal invariant broken: splice location not found', + loc: originalInstrs[cursor].loc, + }); + + if (rewrite.kind === 'instr') { + currBlock.instructions.push(rewrite.value); + } else { + const {entry, blocks} = rewrite.value; + const entryBlock = blocks.get(entry)!; + // splice in all instructions from the entry block + currBlock.instructions.push(...entryBlock.instructions); + if (blocks.size > 1) { + /** + * We're splicing in a set of value-blocks, which means we need + * to push new blocks and update terminals. + */ + CompilerError.invariant( + terminalFallthrough(entryBlock.terminal) === rewrite.exitBlockId, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough', + loc: entryBlock.terminal.loc, + }, + ); + const originalTerminal = currBlock.terminal; + currBlock.terminal = entryBlock.terminal; + + for (const [id, block] of blocks) { + if (id === entry) { + continue; + } + if (id === rewrite.exitBlockId) { + block.terminal = originalTerminal; + currBlock = block; + } + rewriteBlocks.push(block); + } + } + } + } + currBlock.instructions.push(...originalInstrs.slice(cursor)); } function inferReactiveIdentifiers(fn: HIRFunction): Set { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md new file mode 100644 index 0000000000..e4560848dd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +function Component(t0) { + const { foo } = t0; + const arr = []; + + useEffect(() => print(arr[0]?.value), [arr[0]?.value]); + arr.push({ value: foo }); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":304},"end":{"line":9,"column":39,"index":341},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":326},"end":{"line":9,"column":27,"index":329},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) [{"value":1}] +logs: [1] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js new file mode 100644 index 0000000000..c435b72d1a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js @@ -0,0 +1,17 @@ +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index 05ef40c150..5e6f19dd83 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -14,12 +14,17 @@ function Component({arrRef}) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect, useRef } from "react"; import { print } from "shared-runtime"; @@ -32,7 +37,21 @@ function Component(t0) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ arrRef: { current: { val: "initial ref value" } } }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":227},"end":{"line":8,"column":40,"index":265},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":249},"end":{"line":8,"column":30,"index":255},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) {"current":{"val":2}} +logs: [{ val: 2 }] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js index f497d7e595..bd3f6d1de5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -9,3 +9,8 @@ function Component({arrRef}) { arrRef.current.val = 2; return arrRef; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index fa1df3ef88..3b61fbf834 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -2,33 +2,55 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect } from "react"; function Component(t0) { const { foo } = t0; const arr = []; - useEffect(() => arr.push(foo), [arr, foo]); + useEffect(() => { + arr.push(foo); + }, [arr, foo]); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":149},"end":{"line":8,"column":4,"index":190},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":180},"end":{"line":7,"column":16,"index":183},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) [2] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js index 2e2eb7bc08..fbcbf004a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js @@ -1,9 +1,16 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md new file mode 100644 index 0000000000..be107a2afa --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md @@ -0,0 +1,101 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print, shallowCopy } from "shared-runtime"; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr(t0) { + const $ = _c(13); + const { cond, propVal } = t0; + let t1; + if ($[0] !== cond || $[1] !== propVal) { + t1 = cond ? { b: propVal } : null; + $[0] = cond; + $[1] = propVal; + $[2] = t1; + } else { + t1 = $[2]; + } + let t2; + if ($[3] !== t1) { + t2 = { a: t1, c: null }; + $[3] = t1; + $[4] = t2; + } else { + t2 = $[4]; + } + const obj = t2; + const t3 = propVal + 1; + let t4; + if ($[5] !== t3) { + t4 = shallowCopy({ a: { b: { c: { d: { e: { f: t3 } } } } } }); + $[5] = t3; + $[6] = t4; + } else { + t4 = $[6]; + } + const other = t4; + let t5; + if ($[7] !== propVal) { + t5 = shallowCopy(propVal); + $[7] = propVal; + $[8] = t5; + } else { + t5 = $[8]; + } + const primitive = t5; + let t6; + if ( + $[9] !== obj.a?.b || + $[10] !== other?.a?.b?.c?.d?.e.f || + $[11] !== primitive.a?.b.c?.d?.e.f + ) { + t6 = () => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f); + $[9] = obj.a?.b; + $[10] = other?.a?.b?.c?.d?.e.f; + $[11] = primitive.a?.b.c?.d?.e.f; + $[12] = t6; + } else { + t6 = $[12]; + } + useEffect(t6, [obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + +``` + +### Eval output +(kind: ok) +logs: [1,2,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js new file mode 100644 index 0000000000..f6e5a18d47 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js @@ -0,0 +1,18 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md index 7c9f21b85c..d2414f3f9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md @@ -8,10 +8,16 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + ``` ## Code @@ -23,7 +29,7 @@ import { print } from "shared-runtime"; // TODO: take optional chains as dependencies function ReactiveMemberExpr(t0) { - const $ = _c(7); + const $ = _c(9); const { cond, propVal } = t0; let t1; if ($[0] !== cond || $[1] !== propVal) { @@ -36,7 +42,7 @@ function ReactiveMemberExpr(t0) { } let t2; if ($[3] !== t1) { - t2 = { a: t1 }; + t2 = { a: t1, c: null }; $[3] = t1; $[4] = t2; } else { @@ -51,10 +57,25 @@ function ReactiveMemberExpr(t0) { } else { t3 = $[6]; } - useEffect(t3, [obj.a]); + useEffect(t3, [obj.a?.b]); + let t4; + if ($[7] !== obj.c?.d) { + t4 = () => print(obj.c?.d); + $[7] = obj.c?.d; + $[8] = t4; + } else { + t4 = $[8]; + } + useEffect(t4, [obj.c?.d]); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) +logs: [1,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js index 8a76784e24..4f567fa152 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js @@ -4,6 +4,12 @@ import {print} from 'shared-runtime'; // TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; From 06d0660ce1f05883f3593526f1777db519773c1d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 21 May 2025 17:23:58 -0400 Subject: [PATCH 409/422] [compiler][gating] Custom opt out directives (experimental option) Adding an experimental / unstable compiler config to enable custom opt-out directives --- .../src/Entrypoint/Options.ts | 25 +++++++++++++ .../src/Entrypoint/Program.ts | 17 +++++++-- .../src/Utils/TestUtils.ts | 17 +++------ .../custom-opt-out-directive.expect.md | 35 +++++++++++++++++++ .../compiler/custom-opt-out-directive.tsx | 10 ++++++ 5 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 96cce887d8..0c23ceb345 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -41,6 +41,10 @@ const DynamicGatingOptionsSchema = z.object({ source: z.string(), }); export type DynamicGatingOptions = z.infer; +const CustomOptOutDirectiveSchema = z + .nullable(z.array(z.string())) + .default(null); +type CustomOptOutDirective = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -132,6 +136,11 @@ export type PluginOptions = { */ ignoreUseNoForget: boolean; + /** + * Unstable / do not use + */ + customOptOutDirectives: CustomOptOutDirective; + sources: Array | ((filename: string) => boolean) | null; /** @@ -278,6 +287,7 @@ export const defaultOptions: PluginOptions = { return filename.indexOf('node_modules') === -1; }, enableReanimatedCheck: true, + customOptOutDirectives: null, target: '19', } as const; @@ -338,6 +348,21 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'customOptOutDirectives': { + const result = CustomOptOutDirectiveSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse custom opt out directives. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index cb57bd2c49..de8d16fb12 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -63,7 +63,16 @@ export function tryFindDirectiveEnablingMemoization( export function findDirectiveDisablingMemoization( directives: Array, + {customOptOutDirectives}: PluginOptions, ): t.Directive | null { + if (customOptOutDirectives != null) { + return ( + directives.find( + directive => + customOptOutDirectives.indexOf(directive.value.value) !== -1, + ) ?? null + ); + } return ( directives.find(directive => OPT_OUT_DIRECTIVES.has(directive.value.value), @@ -394,7 +403,8 @@ export function compileProgram( code: pass.code, suppressions, hasModuleScopeOptOut: - findDirectiveDisablingMemoization(program.node.directives) != null, + findDirectiveDisablingMemoization(program.node.directives, pass.opts) != + null, }); const queue: Array = findFunctionsToCompile( @@ -571,7 +581,10 @@ function processFn( } directives = { optIn: optIn.unwrapOr(null), - optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization( + fn.node.body.directives, + programContext.opts, + ), }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts index b4484331de..153b85b387 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts @@ -101,13 +101,10 @@ function parseConfigPragmaEnvironmentForTest( ): EnvironmentConfig { const maybeConfig: Partial> = {}; - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - const keyVal = token.slice(1); + for (const keyVal of pragma.split(' @')) { const valIdx = keyVal.indexOf(':'); - const key = valIdx === -1 ? keyVal : keyVal.slice(0, valIdx); + const key = + valIdx === -1 ? keyVal.split(' ', 1)[0] : keyVal.slice(0, valIdx); const val = valIdx === -1 ? undefined : keyVal.slice(valIdx + 1); const isSet = val === undefined || val === 'true'; if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) { @@ -176,13 +173,9 @@ export function parseConfigPragmaForTests( compilationMode: defaults.compilationMode, environment, }; - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - const keyVal = token.slice(1); + for (const keyVal of pragma.split(' @')) { const idx = keyVal.indexOf(':'); - const key = idx === -1 ? keyVal : keyVal.slice(0, idx); + const key = idx === -1 ? keyVal.split(' ', 1)[0] : keyVal.slice(0, idx); const val = idx === -1 ? undefined : keyVal.slice(idx + 1); if (!hasOwnProperty(defaultOptions, key)) { continue; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md new file mode 100644 index 0000000000..7875137a88 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @customOptOutDirectives:["use todo memo"] +function Component() { + 'use todo memo'; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +// @customOptOutDirectives:["use todo memo"] +function Component() { + "use todo memo"; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +### Eval output +(kind: ok)
hello world!
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx new file mode 100644 index 0000000000..2255596183 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx @@ -0,0 +1,10 @@ +// @customOptOutDirectives:["use todo memo"] +function Component() { + 'use todo memo'; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; From 64b555c5fb67c7ec90b969bcae035fcef2b278c8 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 22 May 2025 15:44:35 -0400 Subject: [PATCH 410/422] [compiler] Prepare HIRBuilder to be used by later passes --- .../src/Entrypoint/Pipeline.ts | 1 + .../src/HIR/BuildHIR.ts | 25 ++++++++--------- .../src/HIR/Environment.ts | 5 +++- .../src/HIR/HIRBuilder.ts | 28 +++++++++++-------- 4 files changed, 34 insertions(+), 25 deletions(-) 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 831d1ca380..fe97c8d642 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -130,6 +130,7 @@ function run( mode, config, contextIdentifiers, + func, logger, filename, code, 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 b9f82eea18..cfb15fb595 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -70,12 +70,14 @@ import {BuiltInArrayId} from './ObjectShape'; export function lower( func: NodePath, env: Environment, + // Bindings captured from the outer function, in case lower() is called recursively (for lambdas) bindings: Bindings | null = null, capturedRefs: Array = [], - // the outermost function being compiled, in case lower() is called recursively (for lambdas) - parent: NodePath | null = null, ): Result { - const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs); + const builder = new HIRBuilder(env, { + bindings, + context: capturedRefs, + }); const context: HIRFunction['context'] = []; for (const ref of capturedRefs ?? []) { @@ -215,7 +217,7 @@ export function lower( return Ok({ id, params, - fnType: parent == null ? env.fnType : 'Other', + fnType: bindings == null ? env.fnType : 'Other', returnTypeAnnotation: null, // TODO: extract the actual return type node if present returnType: makeType(), body: builder.build(), @@ -3417,7 +3419,7 @@ function lowerFunction( | t.ObjectMethod >, ): LoweredFunction | null { - const componentScope: Scope = builder.parentFunction.scope; + const componentScope: Scope = builder.environment.parentFunction.scope; const capturedContext = gatherCapturedContext(expr, componentScope); /* @@ -3428,13 +3430,10 @@ function lowerFunction( * This isn't a problem in practice because use Babel's scope analysis to * identify the correct references. */ - const lowering = lower( - expr, - builder.environment, - builder.bindings, - [...builder.context, ...capturedContext], - builder.parentFunction, - ); + const lowering = lower(expr, builder.environment, builder.bindings, [ + ...builder.context, + ...capturedContext, + ]); let loweredFunc: HIRFunction; if (lowering.isErr()) { lowering @@ -3456,7 +3455,7 @@ function lowerExpressionToTemporary( return lowerValueToTemporary(builder, value); } -function lowerValueToTemporary( +export function lowerValueToTemporary( builder: HIRBuilder, value: InstructionValue, ): Place { 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 6e6643cd1d..27b578b3c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -47,7 +47,7 @@ import { ShapeRegistry, addHook, } from './ObjectShape'; -import {Scope as BabelScope} from '@babel/traverse'; +import {Scope as BabelScope, NodePath} from '@babel/traverse'; import {TypeSchema} from './TypeSchema'; export const ReactElementSymbolSchema = z.object({ @@ -675,6 +675,7 @@ export class Environment { #contextIdentifiers: Set; #hoistedIdentifiers: Set; + parentFunction: NodePath; constructor( scope: BabelScope, @@ -682,6 +683,7 @@ export class Environment { compilerMode: CompilerMode, config: EnvironmentConfig, contextIdentifiers: Set, + parentFunction: NodePath, // the outermost function being compiled logger: Logger | null, filename: string | null, code: string | null, @@ -740,6 +742,7 @@ export class Environment { this.#moduleTypes.set(REANIMATED_MODULE_NAME, reanimatedModuleType); } + this.parentFunction = parentFunction; this.#contextIdentifiers = contextIdentifiers; this.#hoistedIdentifiers = new Set(); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 44dd34b7d6..9ed37bb2fc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -110,7 +110,6 @@ export default class HIRBuilder { #bindings: Bindings; #env: Environment; #exceptionHandlerStack: Array = []; - parentFunction: NodePath; errors: CompilerError = new CompilerError(); /** * Traversal context: counts the number of `fbt` tag parents @@ -136,16 +135,17 @@ export default class HIRBuilder { constructor( env: Environment, - parentFunction: NodePath, // the outermost function being compiled - bindings: Bindings | null = null, - context: Array | null = null, + options?: { + bindings?: Bindings | null; + context?: Array; + entryBlockKind?: BlockKind; + }, ) { this.#env = env; - this.#bindings = bindings ?? new Map(); - this.parentFunction = parentFunction; - this.#context = context ?? []; + this.#bindings = options?.bindings ?? new Map(); + this.#context = options?.context ?? []; this.#entry = makeBlockId(env.nextBlockId); - this.#current = newBlock(this.#entry, 'block'); + this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block'); } currentBlockKind(): BlockKind { @@ -239,7 +239,7 @@ export default class HIRBuilder { // Check if the binding is from module scope const outerBinding = - this.parentFunction.scope.parent.getBinding(originalName); + this.#env.parentFunction.scope.parent.getBinding(originalName); if (babelBinding === outerBinding) { const path = babelBinding.path; if (path.isImportDefaultSpecifier()) { @@ -293,7 +293,7 @@ export default class HIRBuilder { const binding = this.#resolveBabelBinding(path); if (binding) { // Check if the binding is from module scope, if so return null - const outerBinding = this.parentFunction.scope.parent.getBinding( + const outerBinding = this.#env.parentFunction.scope.parent.getBinding( path.node.name, ); if (binding === outerBinding) { @@ -376,7 +376,7 @@ export default class HIRBuilder { } // Terminate the current block w the given terminal, and start a new block - terminate(terminal: Terminal, nextBlockKind: BlockKind | null): void { + terminate(terminal: Terminal, nextBlockKind: BlockKind | null): BlockId { const {id: blockId, kind, instructions} = this.#current; this.#completed.set(blockId, { kind, @@ -390,6 +390,7 @@ export default class HIRBuilder { const nextId = this.#env.nextBlockId; this.#current = newBlock(nextId, nextBlockKind); } + return blockId; } /* @@ -746,6 +747,11 @@ function getReversePostorderedBlocks(func: HIR): HIR['blocks'] { * (eg bb2 then bb1), we ensure that they get reversed back to the correct order. */ const block = func.blocks.get(blockId)!; + CompilerError.invariant(block != null, { + reason: '[HIRBuilder] Unexpected null block', + description: `expected block ${blockId} to exist`, + loc: GeneratedSource, + }); const successors = [...eachTerminalSuccessor(block.terminal)].reverse(); const fallthrough = terminalFallthrough(block.terminal); From 90131ceca5bebd95e7e78e63f6ac292124af003e Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 22 May 2025 15:44:35 -0400 Subject: [PATCH 411/422] [compiler] Add reactive flag on scope dependencies When collecting scope dependencies, mark each dependency with `reactive: true | false`. This prepares for later PRs https://github.com/facebook/react/pull/33326 and https://github.com/facebook/react/pull/32099 which rewrite scope dependencies into instructions. Note that some reactive objects may have non-reactive properties, but we do not currently track this. Technically, state[0] is reactive and state[1] is not. Currently, both would be marked as reactive. ```js const state = useState(); ``` --- .../src/HIR/CollectHoistablePropertyLoads.ts | 28 ++++++++++---- .../HIR/CollectOptionalChainDependencies.ts | 2 + .../src/HIR/DeriveMinimalDependenciesHIR.ts | 36 +++++++++++++----- .../src/HIR/HIR.ts | 12 ++++++ .../src/HIR/PropagateScopeDependenciesHIR.ts | 11 +++++- .../src/Inference/InferReactivePlaces.ts | 38 ++++++++++++++++++- ...rgeReactiveScopesThatInvalidateTogether.ts | 1 + 7 files changed, 109 insertions(+), 19 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 ea7268c573..a11822538f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -241,7 +241,10 @@ type PropertyPathNode = class PropertyPathRegistry { roots: Map = new Map(); - getOrCreateIdentifier(identifier: Identifier): PropertyPathNode { + getOrCreateIdentifier( + identifier: Identifier, + reactive: boolean, + ): PropertyPathNode { /** * Reads from a statically scoped variable are always safe in JS, * with the exception of TDZ (not addressed by this pass). @@ -255,12 +258,19 @@ class PropertyPathRegistry { optionalProperties: new Map(), fullPath: { identifier, + reactive, path: [], }, hasOptional: false, parent: null, }; this.roots.set(identifier.id, rootNode); + } else { + CompilerError.invariant(reactive === rootNode.fullPath.reactive, { + reason: + '[HoistablePropertyLoads] Found inconsistencies in `reactive` flag when deduping identifier reads within the same scope', + loc: identifier.loc, + }); } return rootNode; } @@ -278,6 +288,7 @@ class PropertyPathRegistry { parent: parent, fullPath: { identifier: parent.fullPath.identifier, + reactive: parent.fullPath.reactive, path: parent.fullPath.path.concat(entry), }, hasOptional: parent.hasOptional || entry.optional, @@ -293,7 +304,7 @@ class PropertyPathRegistry { * so all subpaths of a PropertyLoad should already exist * (e.g. a.b is added before a.b.c), */ - let currNode = this.getOrCreateIdentifier(n.identifier); + let currNode = this.getOrCreateIdentifier(n.identifier, n.reactive); if (n.path.length === 0) { return currNode; } @@ -315,10 +326,11 @@ function getMaybeNonNullInInstruction( instr: InstructionValue, context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { - let path = null; + let path: ReactiveScopeDependency | null = null; if (instr.kind === 'PropertyLoad') { path = context.temporaries.get(instr.object.identifier.id) ?? { identifier: instr.object.identifier, + reactive: instr.object.reactive, path: [], }; } else if (instr.kind === 'Destructure') { @@ -381,7 +393,7 @@ function collectNonNullsInBlocks( ) { const identifier = fn.params[0].identifier; knownNonNullIdentifiers.add( - context.registry.getOrCreateIdentifier(identifier), + context.registry.getOrCreateIdentifier(identifier, true), ); } const nodes = new Map< @@ -616,9 +628,11 @@ function reduceMaybeOptionalChains( changed = false; for (const original of optionalChainNodes) { - let {identifier, path: origPath} = original.fullPath; - let currNode: PropertyPathNode = - registry.getOrCreateIdentifier(identifier); + let {identifier, path: origPath, reactive} = original.fullPath; + let currNode: PropertyPathNode = registry.getOrCreateIdentifier( + identifier, + reactive, + ); for (let i = 0; i < origPath.length; i++) { const entry = origPath[i]; // If the base is known to be non-null, replace with a non-optional load 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 cb787d04d0..75dad4c1bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -290,6 +290,7 @@ function traverseOptionalBlock( ); baseObject = { identifier: maybeTest.instructions[0].value.place.identifier, + reactive: maybeTest.instructions[0].value.place.reactive, path, }; test = maybeTest.terminal; @@ -391,6 +392,7 @@ function traverseOptionalBlock( ); const load = { identifier: baseObject.identifier, + reactive: baseObject.reactive, path: [ ...baseObject.path, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts index 7f6fb9e88f..7819ab39b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts @@ -25,8 +25,9 @@ export class ReactiveScopeDependencyTreeHIR { * `identifier.path`, or `identifier?.path` is in this map, it is safe to * evaluate (non-optional) PropertyLoads from. */ - #hoistableObjects: Map = new Map(); - #deps: Map = new Map(); + #hoistableObjects: Map = + new Map(); + #deps: Map = new Map(); /** * @param hoistableObjects a set of paths from which we can safely evaluate @@ -35,9 +36,10 @@ export class ReactiveScopeDependencyTreeHIR { * duplicates when traversing the CFG. */ constructor(hoistableObjects: Iterable) { - for (const {path, identifier} of hoistableObjects) { + for (const {path, identifier, reactive} of hoistableObjects) { let currNode = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot( identifier, + reactive, this.#hoistableObjects, path.length > 0 && path[0].optional ? 'Optional' : 'NonNull', ); @@ -70,7 +72,8 @@ export class ReactiveScopeDependencyTreeHIR { static #getOrCreateRoot( identifier: Identifier, - roots: Map>, + reactive: boolean, + roots: Map & {reactive: boolean}>, defaultAccessType: T, ): TreeNode { // roots can always be accessed unconditionally in JS @@ -79,9 +82,16 @@ export class ReactiveScopeDependencyTreeHIR { if (rootNode === undefined) { rootNode = { properties: new Map(), + reactive, accessType: defaultAccessType, }; roots.set(identifier, rootNode); + } else { + CompilerError.invariant(reactive === rootNode.reactive, { + reason: '[DeriveMinimalDependenciesHIR] Conflicting reactive root flag', + description: `Identifier ${printIdentifier(identifier)}`, + loc: GeneratedSource, + }); } return rootNode; } @@ -92,9 +102,10 @@ export class ReactiveScopeDependencyTreeHIR { * safe-to-evaluate subpath */ addDependency(dep: ReactiveScopeDependency): void { - const {identifier, path} = dep; + const {identifier, reactive, path} = dep; let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot( identifier, + reactive, this.#deps, PropertyAccessType.UnconditionalAccess, ); @@ -172,7 +183,13 @@ export class ReactiveScopeDependencyTreeHIR { deriveMinimalDependencies(): Set { const results = new Set(); for (const [rootId, rootNode] of this.#deps.entries()) { - collectMinimalDependenciesInSubtree(rootNode, rootId, [], results); + collectMinimalDependenciesInSubtree( + rootNode, + rootNode.reactive, + rootId, + [], + results, + ); } return results; @@ -294,25 +311,24 @@ type HoistableNode = TreeNode<'Optional' | 'NonNull'>; type DependencyNode = TreeNode; /** - * TODO: this is directly pasted from DeriveMinimalDependencies. Since we no - * longer have conditionally accessed nodes, we can simplify - * * Recursively calculates minimal dependencies in a subtree. * @param node DependencyNode representing a dependency subtree. * @returns a minimal list of dependencies in this subtree. */ function collectMinimalDependenciesInSubtree( node: DependencyNode, + reactive: boolean, rootIdentifier: Identifier, path: Array, results: Set, ): void { if (isDependency(node.accessType)) { - results.add({identifier: rootIdentifier, path}); + results.add({identifier: rootIdentifier, reactive, path}); } else { for (const [childName, childNode] of node.properties) { collectMinimalDependenciesInSubtree( childNode, + reactive, rootIdentifier, [ ...path, 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 99b8c189ee..1699a0fc3d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1568,6 +1568,18 @@ export type DependencyPathEntry = { export type DependencyPath = Array; export type ReactiveScopeDependency = { identifier: Identifier; + /** + * Reflects whether the base identifier is reactive. Note that some reactive + * objects may have non-reactive properties, but we do not currently track + * this. + * + * ```js + * // Technically, result[0] is reactive and result[1] is not. + * // Currently, both dependencies would be marked as reactive. + * const result = useState(); + * ``` + */ + reactive: boolean; path: DependencyPath; }; 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 96b9e51710..91b7712b88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -316,6 +316,7 @@ function collectTemporariesSidemapImpl( ) { temporaries.set(lvalue.identifier.id, { identifier: value.place.identifier, + reactive: value.place.reactive, path: [], }); } @@ -369,11 +370,13 @@ function getProperty( if (resolvedDependency == null) { property = { identifier: object.identifier, + reactive: object.reactive, path: [{property: propertyName, optional}], }; } else { property = { identifier: resolvedDependency.identifier, + reactive: resolvedDependency.reactive, path: [...resolvedDependency.path, {property: propertyName, optional}], }; } @@ -532,6 +535,7 @@ export class DependencyCollectionContext { this.visitDependency( this.#temporaries.get(place.identifier.id) ?? { identifier: place.identifier, + reactive: place.reactive, path: [], }, ); @@ -596,6 +600,7 @@ export class DependencyCollectionContext { ) { maybeDependency = { identifier: maybeDependency.identifier, + reactive: maybeDependency.reactive, path: [], }; } @@ -617,7 +622,11 @@ export class DependencyCollectionContext { identifier => identifier.declarationId === place.identifier.declarationId, ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) + this.#checkValidDependency({ + identifier: place.identifier, + reactive: place.reactive, + path: [], + }) ) { currentScope.reassignments.add(place.identifier); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts index b05b292124..88faccd8cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts @@ -26,6 +26,7 @@ import { import {PostDominator} from '../HIR/Dominator'; import { eachInstructionLValue, + eachInstructionOperand, eachInstructionValueOperand, eachTerminalOperand, } from '../HIR/visitors'; @@ -292,7 +293,7 @@ export function inferReactivePlaces(fn: HIRFunction): void { let hasReactiveInput = false; /* * NOTE: we want to mark all operands as reactive or not, so we - * avoid short-circuting here + * avoid short-circuiting here */ for (const operand of eachInstructionValueOperand(value)) { const reactive = reactiveIdentifiers.isReactive(operand); @@ -375,6 +376,41 @@ export function inferReactivePlaces(fn: HIRFunction): void { } } } while (reactiveIdentifiers.snapshot()); + + function propagateReactivityToInnerFunctions( + fn: HIRFunction, + isOutermost: boolean, + ): void { + for (const [, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if (!isOutermost) { + for (const operand of eachInstructionOperand(instr)) { + reactiveIdentifiers.isReactive(operand); + } + } + if ( + instr.value.kind === 'ObjectMethod' || + instr.value.kind === 'FunctionExpression' + ) { + propagateReactivityToInnerFunctions( + instr.value.loweredFunc.func, + false, + ); + } + } + if (!isOutermost) { + for (const operand of eachTerminalOperand(block.terminal)) { + reactiveIdentifiers.isReactive(operand); + } + } + } + } + + /** + * Propagate reactivity for inner functions, as we eventually hoist and dedupe + * dependency instructions for scopes. + */ + propagateReactivityToInnerFunctions(fn, true); } /* diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts index 08d2212d86..6f2d97ff8e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts @@ -456,6 +456,7 @@ function canMergeScopes( new Set( [...current.scope.declarations.values()].map(declaration => ({ identifier: declaration.identifier, + reactive: true, path: [], })), ), From 0b8b8c6e1fb44f52fb3056d717565263f322e3fc Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 22 May 2025 15:44:35 -0400 Subject: [PATCH 412/422] [compiler] Inferred effect dependencies now include optional chains Inferred effect dependencies now include optional chains. This is a temporary solution while https://github.com/facebook/react/pull/32099 and its followups are worked on. Ideally, we should model reactive scope dependencies in the IR similarly to `ComputeIR` -- dependencies should be hoisted and all references rewritten to use the hoisted dependencies. ` --- .../src/HIR/ScopeDependencyUtils.ts | 283 +++++++++++++++++ .../src/Inference/InferEffectDependencies.ts | 290 ++++++++++++------ ...e-after-useeffect-optional-chain.expect.md | 58 ++++ .../mutate-after-useeffect-optional-chain.js | 17 + ...utate-after-useeffect-ref-access.expect.md | 25 +- .../mutate-after-useeffect-ref-access.js | 7 +- .../mutate-after-useeffect.expect.md | 32 +- .../bailout-retry/mutate-after-useeffect.js | 11 +- .../reactive-optional-chain-complex.expect.md | 99 ++++++ .../reactive-optional-chain-complex.js | 17 + .../reactive-optional-chain.expect.md | 33 +- .../reactive-optional-chain.js | 9 +- 12 files changed, 764 insertions(+), 117 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts new file mode 100644 index 0000000000..5d30aeb644 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ScopeDependencyUtils.ts @@ -0,0 +1,283 @@ +import { + Place, + ReactiveScopeDependency, + Identifier, + makeInstructionId, + InstructionKind, + GeneratedSource, + BlockId, + makeTemporaryIdentifier, + Effect, + GotoVariant, + HIR, +} from './HIR'; +import {CompilerError} from '../CompilerError'; +import {Environment} from './Environment'; +import HIRBuilder from './HIRBuilder'; +import {lowerValueToTemporary} from './BuildHIR'; + +type DependencyInstructions = { + place: Place; + value: HIR; + exitBlockId: BlockId; +}; + +export function buildDependencyInstructions( + dep: ReactiveScopeDependency, + env: Environment, +): DependencyInstructions { + const builder = new HIRBuilder(env, { + entryBlockKind: 'value', + }); + let dependencyValue: Identifier; + if (dep.path.every(path => !path.optional)) { + dependencyValue = writeNonOptionalDependency(dep, env, builder); + } else { + dependencyValue = writeOptionalDependency(dep, builder, null); + } + + const exitBlockId = builder.terminate( + { + kind: 'unsupported', + loc: GeneratedSource, + id: makeInstructionId(0), + }, + null, + ); + return { + place: { + kind: 'Identifier', + identifier: dependencyValue, + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + value: builder.build(), + exitBlockId, + }; +} + +/** + * Write instructions for a simple dependency (without optional chains) + */ +function writeNonOptionalDependency( + dep: ReactiveScopeDependency, + env: Environment, + builder: HIRBuilder, +): Identifier { + const loc = dep.identifier.loc; + let curr: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: curr, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'LoadLocal', + place: { + identifier: dep.identifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + + /** + * Iteratively build up dependency instructions by reading from the last written + * instruction. + */ + for (const path of dep.path) { + const next = makeTemporaryIdentifier(env.nextIdentifierId, loc); + builder.push({ + lvalue: { + identifier: next, + kind: 'Identifier', + effect: Effect.Mutate, + reactive: dep.reactive, + loc, + }, + value: { + kind: 'PropertyLoad', + object: { + identifier: curr, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc, + }, + property: path.property, + loc, + }, + id: makeInstructionId(1), + loc: loc, + }); + curr = next; + } + return curr; +} + +/** + * Write a dependency into optional blocks. + * + * e.g. `a.b?.c.d` is written to an optional block that tests `a.b` and + * conditionally evaluates `c.d`. + */ +function writeOptionalDependency( + dep: ReactiveScopeDependency, + builder: HIRBuilder, + parentAlternate: BlockId | null, +): Identifier { + const env = builder.environment; + /** + * Reserve an identifier which will be used to store the result of this + * dependency. + */ + const dependencyValue: Place = { + kind: 'Identifier', + identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource), + effect: Effect.Mutate, + reactive: dep.reactive, + loc: GeneratedSource, + }; + + /** + * Reserve a block which is the fallthrough (and transitive successor) of this + * optional chain. + */ + const continuationBlock = builder.reserve(builder.currentBlockKind()); + let alternate; + if (parentAlternate != null) { + alternate = parentAlternate; + } else { + /** + * If an outermost alternate block has not been reserved, write one + * + * $N = Primitive undefined + * $M = StoreLocal $OptionalResult = $N + * goto fallthrough + */ + alternate = builder.enter('value', () => { + const temp = lowerValueToTemporary(builder, { + kind: 'Primitive', + value: undefined, + loc: GeneratedSource, + }); + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}}, + value: {...temp}, + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + } + + // Reserve the consequent block, which is the successor of the test block + const consequent = builder.reserve('value'); + + let testIdentifier: Identifier | null = null; + const testBlock = builder.enter('value', () => { + const testDependency = { + ...dep, + path: dep.path.slice(0, dep.path.length - 1), + }; + const firstOptional = dep.path.findIndex(path => path.optional); + CompilerError.invariant(firstOptional !== -1, { + reason: + '[ScopeDependencyUtils] Internal invariant broken: expected optional path', + loc: dep.identifier.loc, + description: null, + suggestions: null, + }); + if (firstOptional === dep.path.length - 1) { + // Base case: the test block is simple + testIdentifier = writeNonOptionalDependency(testDependency, env, builder); + } else { + // Otherwise, the test block is a nested optional chain + testIdentifier = writeOptionalDependency( + testDependency, + builder, + alternate, + ); + } + + return { + kind: 'branch', + test: { + identifier: testIdentifier, + effect: Effect.Freeze, + kind: 'Identifier', + loc: GeneratedSource, + reactive: dep.reactive, + }, + consequent: consequent.id, + alternate, + id: makeInstructionId(0), + loc: GeneratedSource, + fallthrough: continuationBlock.id, + }; + }); + + builder.enterReserved(consequent, () => { + CompilerError.invariant(testIdentifier !== null, { + reason: 'Satisfy type checker', + description: null, + loc: null, + suggestions: null, + }); + + lowerValueToTemporary(builder, { + kind: 'StoreLocal', + lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}}, + value: lowerValueToTemporary(builder, { + kind: 'PropertyLoad', + object: { + identifier: testIdentifier, + kind: 'Identifier', + effect: Effect.Freeze, + reactive: dep.reactive, + loc: GeneratedSource, + }, + property: dep.path.at(-1)!.property, + loc: GeneratedSource, + }), + type: null, + loc: GeneratedSource, + }); + return { + kind: 'goto', + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }; + }); + builder.terminateWithContinuation( + { + kind: 'optional', + optional: dep.path.at(-1)!.optional, + test: testBlock, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + loc: GeneratedSource, + }, + continuationBlock, + ); + + return dependencyValue.identifier; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index f1a5843419..4daa2f9fba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -10,7 +10,6 @@ import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, Effect, - Environment, FunctionExpression, GeneratedSource, HIRFunction, @@ -29,6 +28,9 @@ import { isSetStateType, isFireFunctionType, makeScopeId, + HIR, + BasicBlock, + BlockId, } from '../HIR'; import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads'; import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies'; @@ -38,13 +40,20 @@ import { createTemporaryPlace, fixScopeAndIdentifierRanges, markInstructionIds, + markPredecessors, + reversePostorderBlocks, } from '../HIR/HIRBuilder'; import { collectTemporariesSidemap, DependencyCollectionContext, handleInstruction, } from '../HIR/PropagateScopeDependenciesHIR'; -import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {buildDependencyInstructions} from '../HIR/ScopeDependencyUtils'; +import { + eachInstructionOperand, + eachTerminalOperand, + terminalFallthrough, +} from '../HIR/visitors'; import {empty} from '../Utils/Stack'; import {getOrInsertWith} from '../Utils/utils'; @@ -53,7 +62,6 @@ import {getOrInsertWith} from '../Utils/utils'; * a second argument to the useEffect call if no dependency array is provided. */ export function inferEffectDependencies(fn: HIRFunction): void { - let hasRewrite = false; const fnExpressions = new Map< IdentifierId, TInstruction @@ -86,6 +94,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) */ const reactiveIds = inferReactiveIdentifiers(fn); + const rewriteBlocks: Array = []; for (const [, block] of fn.body.blocks) { if (block.terminal.kind === 'scope') { @@ -101,7 +110,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { ); } } - const rewriteInstrs = new Map>(); + const rewriteInstrs: Array = []; for (const instr of block.instructions) { const {value, lvalue} = instr; if (value.kind === 'FunctionExpression') { @@ -165,7 +174,6 @@ export function inferEffectDependencies(fn: HIRFunction): void { ) { // We have a useEffect call with no deps array, so we need to infer the deps const effectDeps: Array = []; - const newInstructions: Array = []; const deps: ArrayExpression = { kind: 'ArrayExpression', elements: effectDeps, @@ -196,24 +204,28 @@ export function inferEffectDependencies(fn: HIRFunction): void { */ const usedDeps = []; - for (const dep of minimalDeps) { + for (const maybeDep of minimalDeps) { if ( - ((isUseRefType(dep.identifier) || - isSetStateType(dep.identifier)) && - !reactiveIds.has(dep.identifier.id)) || - isFireFunctionType(dep.identifier) + ((isUseRefType(maybeDep.identifier) || + isSetStateType(maybeDep.identifier)) && + !reactiveIds.has(maybeDep.identifier.id)) || + isFireFunctionType(maybeDep.identifier) ) { // exclude non-reactive hook results, which will never be in a memo block continue; } - const {place, instructions} = writeDependencyToInstructions( + const dep = truncateDepAtCurrent(maybeDep); + const {place, value, exitBlockId} = buildDependencyInstructions( dep, - reactiveIds.has(dep.identifier.id), fn.env, - fnExpr.loc, ); - newInstructions.push(...instructions); + rewriteInstrs.push({ + kind: 'block', + location: instr.id, + value, + exitBlockId: exitBlockId, + }); effectDeps.push(place); usedDeps.push(dep); } @@ -234,27 +246,32 @@ export function inferEffectDependencies(fn: HIRFunction): void { }); } - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, - }); - // Step 2: push the inferred deps array as an argument of the useEffect + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, + }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } else if (loadGlobals.has(value.args[0].identifier.id)) { // Global functions have no reactive dependencies, so we can insert an empty array - newInstructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, + rewriteInstrs.push({ + kind: 'instr', + location: instr.id, + value: { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }, }); value.args.push({...depsPlace, effect: Effect.Freeze}); - rewriteInstrs.set(instr.id, newInstructions); fn.env.inferredEffectLocations.add(callee.loc); } } else if ( @@ -285,85 +302,164 @@ export function inferEffectDependencies(fn: HIRFunction): void { } } } - if (rewriteInstrs.size > 0) { - hasRewrite = true; - const newInstrs = []; - for (const instr of block.instructions) { - const newInstr = rewriteInstrs.get(instr.id); - if (newInstr != null) { - newInstrs.push(...newInstr, instr); - } else { - newInstrs.push(instr); - } - } - block.instructions = newInstrs; - } + rewriteSplices(block, rewriteInstrs, rewriteBlocks); } - if (hasRewrite) { + + if (rewriteBlocks.length > 0) { + for (const block of rewriteBlocks) { + fn.body.blocks.set(block.id, block); + } + + /** + * Fixup the HIR to restore RPO, ensure correct predecessors, and renumber + * instructions. + */ + reversePostorderBlocks(fn.body); + markPredecessors(fn.body); // Renumber instructions and fix scope ranges markInstructionIds(fn.body); fixScopeAndIdentifierRanges(fn.body); + fn.env.hasInferredEffect = true; } } -function writeDependencyToInstructions( +function truncateDepAtCurrent( dep: ReactiveScopeDependency, - reactive: boolean, - env: Environment, - loc: SourceLocation, -): {place: Place; instructions: Array} { - const instructions: Array = []; - let currValue = createTemporaryPlace(env, GeneratedSource); - currValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...currValue, effect: Effect.Mutate}, - value: { - kind: 'LoadLocal', - place: { - kind: 'Identifier', - identifier: dep.identifier, - effect: Effect.Capture, - reactive, - loc: loc, - }, - loc: loc, - }, - }); - for (const path of dep.path) { - if (path.optional) { - /** - * TODO: instead of truncating optional paths, reuse - * instructions from hoisted dependencies block(s) - */ - break; - } - if (path.property === 'current') { - /* - * Prune ref.current accesses. This may over-capture for non-ref values with - * a current property, but that's fine. - */ - break; - } - const nextValue = createTemporaryPlace(env, GeneratedSource); - nextValue.reactive = reactive; - instructions.push({ - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...nextValue, effect: Effect.Mutate}, - value: { - kind: 'PropertyLoad', - object: {...currValue, effect: Effect.Capture}, - property: path.property, - loc: loc, - }, - }); - currValue = nextValue; +): ReactiveScopeDependency { + const idx = dep.path.findIndex(path => path.property === 'current'); + if (idx === -1) { + return dep; + } else { + return {...dep, path: dep.path.slice(0, idx)}; } - currValue.effect = Effect.Freeze; - return {place: currValue, instructions}; +} + +type SpliceInfo = + | {kind: 'instr'; location: InstructionId; value: Instruction} + | { + kind: 'block'; + location: InstructionId; + value: HIR; + exitBlockId: BlockId; + }; + +function rewriteSplices( + originalBlock: BasicBlock, + splices: Array, + rewriteBlocks: Array, +): void { + if (splices.length === 0) { + return; + } + /** + * Splice instructions or value blocks into the original block. + * --- original block --- + * bb_original + * instr1 + * ... + * instr2 <-- splice location + * instr3 + * ... + * + * + * If there is more than one block in the splice, this means that we're + * splicing in a set of value-blocks of the following structure: + * --- blocks we're splicing in --- + * bb_entry: + * instrEntry + * ... + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... + * + * + * + * --- rewritten blocks --- + * bb_original + * instr1 + * ... (original instructions) + * instr2 + * instrEntry + * ... (spliced instructions) + * fallthrough=bb_exit + * + * bb1(value): + * ... + * + * bb_exit: + * instrExit + * ... (spliced instructions) + * instr3 + * ... (original instructions) + * + */ + const originalInstrs = originalBlock.instructions; + let currBlock: BasicBlock = {...originalBlock, instructions: []}; + rewriteBlocks.push(currBlock); + + let cursor = 0; + for (const rewrite of splices) { + while (originalInstrs[cursor].id < rewrite.location) { + CompilerError.invariant( + originalInstrs[cursor].id < originalInstrs[cursor + 1].id, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted', + loc: originalInstrs[cursor].loc, + }, + ); + currBlock.instructions.push(originalInstrs[cursor]); + cursor++; + } + CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, { + reason: + '[InferEffectDependencies] Internal invariant broken: splice location not found', + loc: originalInstrs[cursor].loc, + }); + + if (rewrite.kind === 'instr') { + currBlock.instructions.push(rewrite.value); + } else { + const {entry, blocks} = rewrite.value; + const entryBlock = blocks.get(entry)!; + // splice in all instructions from the entry block + currBlock.instructions.push(...entryBlock.instructions); + if (blocks.size > 1) { + /** + * We're splicing in a set of value-blocks, which means we need + * to push new blocks and update terminals. + */ + CompilerError.invariant( + terminalFallthrough(entryBlock.terminal) === rewrite.exitBlockId, + { + reason: + '[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough', + loc: entryBlock.terminal.loc, + }, + ); + const originalTerminal = currBlock.terminal; + currBlock.terminal = entryBlock.terminal; + + for (const [id, block] of blocks) { + if (id === entry) { + continue; + } + if (id === rewrite.exitBlockId) { + block.terminal = originalTerminal; + currBlock = block; + } + rewriteBlocks.push(block); + } + } + } + } + currBlock.instructions.push(...originalInstrs.slice(cursor)); } function inferReactiveIdentifiers(fn: HIRFunction): Set { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md new file mode 100644 index 0000000000..e4560848dd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +function Component(t0) { + const { foo } = t0; + const arr = []; + + useEffect(() => print(arr[0]?.value), [arr[0]?.value]); + arr.push({ value: foo }); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":304},"end":{"line":9,"column":39,"index":341},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":326},"end":{"line":9,"column":27,"index":329},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) [{"value":1}] +logs: [1] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js new file mode 100644 index 0000000000..c435b72d1a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js @@ -0,0 +1,17 @@ +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function Component({foo}) { + const arr = []; + // Taking either arr[0].value or arr as a dependency is reasonable + // as long as developers know what to expect. + useEffect(() => print(arr[0]?.value)); + arr.push({value: foo}); + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index 05ef40c150..5e6f19dd83 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -14,12 +14,17 @@ function Component({arrRef}) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect, useRef } from "react"; import { print } from "shared-runtime"; @@ -32,7 +37,21 @@ function Component(t0) { return arrRef; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ arrRef: { current: { val: "initial ref value" } } }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":227},"end":{"line":8,"column":40,"index":265},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":249},"end":{"line":8,"column":30,"index":255},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) {"current":{"val":2}} +logs: [{ val: 2 }] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js index f497d7e595..bd3f6d1de5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -9,3 +9,8 @@ function Component({arrRef}) { arrRef.current.val = 2; return arrRef; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arrRef: {current: {val: 'initial ref value'}}}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index fa1df3ef88..3b61fbf834 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -2,33 +2,55 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; + ``` ## Code ```javascript -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import { useEffect } from "react"; function Component(t0) { const { foo } = t0; const arr = []; - useEffect(() => arr.push(foo), [arr, foo]); + useEffect(() => { + arr.push(foo); + }, [arr, foo]); arr.push(2); return arr; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 1 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":149},"end":{"line":8,"column":4,"index":190},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":180},"end":{"line":7,"column":16,"index":183},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) [2] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js index 2e2eb7bc08..fbcbf004a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js @@ -1,9 +1,16 @@ -// @inferEffectDependencies @panicThreshold:"none" +// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly import {useEffect} from 'react'; function Component({foo}) { const arr = []; - useEffect(() => arr.push(foo)); + useEffect(() => { + arr.push(foo); + }); arr.push(2); return arr; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md new file mode 100644 index 0000000000..a840ab81a5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md @@ -0,0 +1,99 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print, shallowCopy } from "shared-runtime"; + +function ReactiveMemberExpr(t0) { + const $ = _c(13); + const { cond, propVal } = t0; + let t1; + if ($[0] !== cond || $[1] !== propVal) { + t1 = cond ? { b: propVal } : null; + $[0] = cond; + $[1] = propVal; + $[2] = t1; + } else { + t1 = $[2]; + } + let t2; + if ($[3] !== t1) { + t2 = { a: t1, c: null }; + $[3] = t1; + $[4] = t2; + } else { + t2 = $[4]; + } + const obj = t2; + const t3 = propVal + 1; + let t4; + if ($[5] !== t3) { + t4 = shallowCopy({ a: { b: { c: { d: { e: { f: t3 } } } } } }); + $[5] = t3; + $[6] = t4; + } else { + t4 = $[6]; + } + const other = t4; + let t5; + if ($[7] !== propVal) { + t5 = shallowCopy(propVal); + $[7] = propVal; + $[8] = t5; + } else { + t5 = $[8]; + } + const primitive = t5; + let t6; + if ( + $[9] !== obj.a?.b || + $[10] !== other?.a?.b?.c?.d?.e.f || + $[11] !== primitive.a?.b.c?.d?.e.f + ) { + t6 = () => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f); + $[9] = obj.a?.b; + $[10] = other?.a?.b?.c?.d?.e.f; + $[11] = primitive.a?.b.c?.d?.e.f; + $[12] = t6; + } else { + t6 = $[12]; + } + useEffect(t6, [obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + +``` + +### Eval output +(kind: ok) +logs: [1,2,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js new file mode 100644 index 0000000000..93e10250cf --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js @@ -0,0 +1,17 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print, shallowCopy} from 'shared-runtime'; + +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null, c: null}; + const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); + const primitive = shallowCopy(propVal); + useEffect(() => + print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f) + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md index 7c9f21b85c..ffd81732c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md @@ -6,12 +6,17 @@ import {useEffect} from 'react'; import {print} from 'shared-runtime'; -// TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; + ``` ## Code @@ -21,9 +26,8 @@ import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies import { useEffect } from "react"; import { print } from "shared-runtime"; -// TODO: take optional chains as dependencies function ReactiveMemberExpr(t0) { - const $ = _c(7); + const $ = _c(9); const { cond, propVal } = t0; let t1; if ($[0] !== cond || $[1] !== propVal) { @@ -36,7 +40,7 @@ function ReactiveMemberExpr(t0) { } let t2; if ($[3] !== t1) { - t2 = { a: t1 }; + t2 = { a: t1, c: null }; $[3] = t1; $[4] = t2; } else { @@ -51,10 +55,25 @@ function ReactiveMemberExpr(t0) { } else { t3 = $[6]; } - useEffect(t3, [obj.a]); + useEffect(t3, [obj.a?.b]); + let t4; + if ($[7] !== obj.c?.d) { + t4 = () => print(obj.c?.d); + $[7] = obj.c?.d; + $[8] = t4; + } else { + t4 = $[8]; + } + useEffect(t4, [obj.c?.d]); } +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{ cond: true, propVal: 1 }], +}; + ``` ### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file +(kind: ok) +logs: [1,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js index 8a76784e24..d81f9029a4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js @@ -2,8 +2,13 @@ import {useEffect} from 'react'; import {print} from 'shared-runtime'; -// TODO: take optional chains as dependencies function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null}; + const obj = {a: cond ? {b: propVal} : null, c: null}; useEffect(() => print(obj.a?.b)); + useEffect(() => print(obj.c?.d)); } + +export const FIXTURE_ENTRYPOINT = { + fn: ReactiveMemberExpr, + params: [{cond: true, propVal: 1}], +}; From 12cea1ef993cd15b12e0f2a621eb500370090a56 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 22 May 2025 17:29:00 -0400 Subject: [PATCH 413/422] [compiler][gating] Custom opt out directives (experimental option) Adding an experimental / unstable compiler config to enable custom opt-out directives --- .../src/Entrypoint/Options.ts | 25 ++++++++++++ .../src/Entrypoint/Program.ts | 17 ++++++++- .../src/Utils/TestUtils.ts | 38 +++++++++---------- .../custom-opt-out-directive.expect.md | 35 +++++++++++++++++ .../compiler/custom-opt-out-directive.tsx | 10 +++++ 5 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 96cce887d8..0c23ceb345 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -41,6 +41,10 @@ const DynamicGatingOptionsSchema = z.object({ source: z.string(), }); export type DynamicGatingOptions = z.infer; +const CustomOptOutDirectiveSchema = z + .nullable(z.array(z.string())) + .default(null); +type CustomOptOutDirective = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -132,6 +136,11 @@ export type PluginOptions = { */ ignoreUseNoForget: boolean; + /** + * Unstable / do not use + */ + customOptOutDirectives: CustomOptOutDirective; + sources: Array | ((filename: string) => boolean) | null; /** @@ -278,6 +287,7 @@ export const defaultOptions: PluginOptions = { return filename.indexOf('node_modules') === -1; }, enableReanimatedCheck: true, + customOptOutDirectives: null, target: '19', } as const; @@ -338,6 +348,21 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'customOptOutDirectives': { + const result = CustomOptOutDirectiveSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse custom opt out directives. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index cb57bd2c49..de8d16fb12 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -63,7 +63,16 @@ export function tryFindDirectiveEnablingMemoization( export function findDirectiveDisablingMemoization( directives: Array, + {customOptOutDirectives}: PluginOptions, ): t.Directive | null { + if (customOptOutDirectives != null) { + return ( + directives.find( + directive => + customOptOutDirectives.indexOf(directive.value.value) !== -1, + ) ?? null + ); + } return ( directives.find(directive => OPT_OUT_DIRECTIVES.has(directive.value.value), @@ -394,7 +403,8 @@ export function compileProgram( code: pass.code, suppressions, hasModuleScopeOptOut: - findDirectiveDisablingMemoization(program.node.directives) != null, + findDirectiveDisablingMemoization(program.node.directives, pass.opts) != + null, }); const queue: Array = findFunctionsToCompile( @@ -571,7 +581,10 @@ function processFn( } directives = { optIn: optIn.unwrapOr(null), - optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization( + fn.node.body.directives, + programContext.opts, + ), }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts index b4484331de..6c2cfd5d07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts @@ -93,6 +93,21 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = { }, ], }; + +function* splitPragma( + pragma: string, +): Generator<{key: string; value: string | null}> { + for (const entry of pragma.split('@')) { + const keyVal = entry.trim(); + const valIdx = keyVal.indexOf(':'); + if (valIdx === -1) { + yield {key: keyVal.split(' ', 1)[0], value: null}; + } else { + yield {key: keyVal.slice(0, valIdx), value: keyVal.slice(valIdx + 1)}; + } + } +} + /** * For snap test fixtures and playground only. */ @@ -101,19 +116,11 @@ function parseConfigPragmaEnvironmentForTest( ): EnvironmentConfig { const maybeConfig: Partial> = {}; - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - const keyVal = token.slice(1); - const valIdx = keyVal.indexOf(':'); - const key = valIdx === -1 ? keyVal : keyVal.slice(0, valIdx); - const val = valIdx === -1 ? undefined : keyVal.slice(valIdx + 1); - const isSet = val === undefined || val === 'true'; + for (const {key, value: val} of splitPragma(pragma)) { if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) { continue; } - + const isSet = val == null || val === 'true'; if (isSet && key in testComplexConfigDefaults) { maybeConfig[key] = testComplexConfigDefaults[key]; } else if (isSet) { @@ -176,18 +183,11 @@ export function parseConfigPragmaForTests( compilationMode: defaults.compilationMode, environment, }; - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - const keyVal = token.slice(1); - const idx = keyVal.indexOf(':'); - const key = idx === -1 ? keyVal : keyVal.slice(0, idx); - const val = idx === -1 ? undefined : keyVal.slice(idx + 1); + for (const {key, value: val} of splitPragma(pragma)) { if (!hasOwnProperty(defaultOptions, key)) { continue; } - const isSet = val === undefined || val === 'true'; + const isSet = val == null || val === 'true'; if (isSet && key in testComplexPluginOptionDefaults) { options[key] = testComplexPluginOptionDefaults[key]; } else if (isSet) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md new file mode 100644 index 0000000000..7875137a88 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @customOptOutDirectives:["use todo memo"] +function Component() { + 'use todo memo'; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +// @customOptOutDirectives:["use todo memo"] +function Component() { + "use todo memo"; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +### Eval output +(kind: ok)
hello world!
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx new file mode 100644 index 0000000000..2255596183 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx @@ -0,0 +1,10 @@ +// @customOptOutDirectives:["use todo memo"] +function Component() { + 'use todo memo'; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; From 51620ac79c37c5b105df6cc55786ec591d6a4dc5 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 23 May 2025 13:12:02 -0400 Subject: [PATCH 414/422] [compiler][gating] Custom opt out directives (experimental option) Adding an experimental / unstable compiler config to enable custom opt-out directives --- .../src/Entrypoint/Options.ts | 25 ++++++++++++ .../src/Entrypoint/Program.ts | 17 ++++++++- .../src/Utils/TestUtils.ts | 38 +++++++++---------- .../custom-opt-out-directive.expect.md | 35 +++++++++++++++++ .../compiler/custom-opt-out-directive.tsx | 10 +++++ 5 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 96cce887d8..0c23ceb345 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -41,6 +41,10 @@ const DynamicGatingOptionsSchema = z.object({ source: z.string(), }); export type DynamicGatingOptions = z.infer; +const CustomOptOutDirectiveSchema = z + .nullable(z.array(z.string())) + .default(null); +type CustomOptOutDirective = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -132,6 +136,11 @@ export type PluginOptions = { */ ignoreUseNoForget: boolean; + /** + * Unstable / do not use + */ + customOptOutDirectives: CustomOptOutDirective; + sources: Array | ((filename: string) => boolean) | null; /** @@ -278,6 +287,7 @@ export const defaultOptions: PluginOptions = { return filename.indexOf('node_modules') === -1; }, enableReanimatedCheck: true, + customOptOutDirectives: null, target: '19', } as const; @@ -338,6 +348,21 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'customOptOutDirectives': { + const result = CustomOptOutDirectiveSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse custom opt out directives. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + break; + } default: { parsedOptions[key] = value; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index cb57bd2c49..de8d16fb12 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -63,7 +63,16 @@ export function tryFindDirectiveEnablingMemoization( export function findDirectiveDisablingMemoization( directives: Array, + {customOptOutDirectives}: PluginOptions, ): t.Directive | null { + if (customOptOutDirectives != null) { + return ( + directives.find( + directive => + customOptOutDirectives.indexOf(directive.value.value) !== -1, + ) ?? null + ); + } return ( directives.find(directive => OPT_OUT_DIRECTIVES.has(directive.value.value), @@ -394,7 +403,8 @@ export function compileProgram( code: pass.code, suppressions, hasModuleScopeOptOut: - findDirectiveDisablingMemoization(program.node.directives) != null, + findDirectiveDisablingMemoization(program.node.directives, pass.opts) != + null, }); const queue: Array = findFunctionsToCompile( @@ -571,7 +581,10 @@ function processFn( } directives = { optIn: optIn.unwrapOr(null), - optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization( + fn.node.body.directives, + programContext.opts, + ), }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts index b4484331de..6c2cfd5d07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts @@ -93,6 +93,21 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = { }, ], }; + +function* splitPragma( + pragma: string, +): Generator<{key: string; value: string | null}> { + for (const entry of pragma.split('@')) { + const keyVal = entry.trim(); + const valIdx = keyVal.indexOf(':'); + if (valIdx === -1) { + yield {key: keyVal.split(' ', 1)[0], value: null}; + } else { + yield {key: keyVal.slice(0, valIdx), value: keyVal.slice(valIdx + 1)}; + } + } +} + /** * For snap test fixtures and playground only. */ @@ -101,19 +116,11 @@ function parseConfigPragmaEnvironmentForTest( ): EnvironmentConfig { const maybeConfig: Partial> = {}; - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - const keyVal = token.slice(1); - const valIdx = keyVal.indexOf(':'); - const key = valIdx === -1 ? keyVal : keyVal.slice(0, valIdx); - const val = valIdx === -1 ? undefined : keyVal.slice(valIdx + 1); - const isSet = val === undefined || val === 'true'; + for (const {key, value: val} of splitPragma(pragma)) { if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) { continue; } - + const isSet = val == null || val === 'true'; if (isSet && key in testComplexConfigDefaults) { maybeConfig[key] = testComplexConfigDefaults[key]; } else if (isSet) { @@ -176,18 +183,11 @@ export function parseConfigPragmaForTests( compilationMode: defaults.compilationMode, environment, }; - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - const keyVal = token.slice(1); - const idx = keyVal.indexOf(':'); - const key = idx === -1 ? keyVal : keyVal.slice(0, idx); - const val = idx === -1 ? undefined : keyVal.slice(idx + 1); + for (const {key, value: val} of splitPragma(pragma)) { if (!hasOwnProperty(defaultOptions, key)) { continue; } - const isSet = val === undefined || val === 'true'; + const isSet = val == null || val === 'true'; if (isSet && key in testComplexPluginOptionDefaults) { options[key] = testComplexPluginOptionDefaults[key]; } else if (isSet) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md new file mode 100644 index 0000000000..7875137a88 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @customOptOutDirectives:["use todo memo"] +function Component() { + 'use todo memo'; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +// @customOptOutDirectives:["use todo memo"] +function Component() { + "use todo memo"; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +### Eval output +(kind: ok)
hello world!
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx new file mode 100644 index 0000000000..2255596183 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx @@ -0,0 +1,10 @@ +// @customOptOutDirectives:["use todo memo"] +function Component() { + 'use todo memo'; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; From 7bb66f220ffea8917bee4f0222c46f3ccb09637c Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 30 May 2025 11:55:43 -0400 Subject: [PATCH 415/422] [compiler][patch] Emit unary expressions instead of negative numbers This is a babel bug + edge case. Babel compact mode produces invalid JavaScript (i.e. parse error) when given a `NumericLiteral` with a negative value. See https://codesandbox.io/p/devbox/5d47fr for repro. As a followup, we could change our test infra parse babel options (e.g. a babel transform options pragma) which could let us track regressions. We may add an "exhaustive" mode to the compiler test runner to test (1) different babel options and (2) commonly used versions. --- .../src/ReactiveScopes/CodegenReactiveFunction.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 33a124dcec..88999b32bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -1726,7 +1726,7 @@ function codegenInstructionValue( } case 'UnaryExpression': { value = t.unaryExpression( - instrValue.operator as 'throw', // todo + instrValue.operator, codegenPlaceToExpression(cx, instrValue.value), ); break; @@ -2582,7 +2582,13 @@ function codegenValue( value: boolean | number | string | null | undefined, ): t.Expression { if (typeof value === 'number') { - return t.numericLiteral(value); + if (value < 0) { + // Babel compact mode produces invalid JS for negative numbers + // See https://codesandbox.io/p/devbox/5d47fr + return t.unaryExpression('-', t.numericLiteral(-value), false); + } else { + return t.numericLiteral(value); + } } else if (typeof value === 'boolean') { return t.booleanLiteral(value); } else if (typeof value === 'string') { From 1d4cbaffb5f201550a0f98ba85b574ee3d4059e7 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 2 Jun 2025 11:31:51 -0400 Subject: [PATCH 416/422] [compiler][patch] Emit unary expressions instead of negative numbers This is a babel bug + edge case. Babel compact mode produces invalid JavaScript (i.e. parse error) when given a `NumericLiteral` with a negative value. See https://codesandbox.io/p/devbox/5d47fr for repro. As a followup, we could change our test infra parse babel options (e.g. a babel transform options pragma) which could let us track regressions. We may add an "exhaustive" mode to the compiler test runner to test (1) different babel options and (2) commonly used versions. --- .../ReactiveScopes/CodegenReactiveFunction.ts | 13 ++++- ...el-repro-compact-negative-number.expect.md | 56 +++++++++++++++++++ .../babel-repro-compact-negative-number.js | 15 +++++ compiler/packages/snap/src/compiler.ts | 1 + 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 33a124dcec..17c62c02a6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -1726,7 +1726,7 @@ function codegenInstructionValue( } case 'UnaryExpression': { value = t.unaryExpression( - instrValue.operator as 'throw', // todo + instrValue.operator, codegenPlaceToExpression(cx, instrValue.value), ); break; @@ -2582,7 +2582,16 @@ function codegenValue( value: boolean | number | string | null | undefined, ): t.Expression { if (typeof value === 'number') { - return t.numericLiteral(value); + if (value < 0) { + /** + * Babel's code generator produces invalid JS for negative numbers when + * run with { compact: true }. + * See repro https://codesandbox.io/p/devbox/5d47fr + */ + return t.unaryExpression('-', t.numericLiteral(-value), false); + } else { + return t.numericLiteral(value); + } } else if (typeof value === 'boolean') { return t.booleanLiteral(value); } else if (typeof value === 'string') { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.expect.md new file mode 100644 index 0000000000..70e19e0744 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.expect.md @@ -0,0 +1,56 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +function Repro(props) { + const MY_CONST = -2; + return {props.arg - MY_CONST}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Repro, + params: [ + { + arg: 3, + }, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +function Repro(props) { + const $ = _c(2); + + const t0 = props.arg - -2; + let t1; + if ($[0] !== t0) { + t1 = {t0}; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Repro, + params: [ + { + arg: 3, + }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"children":5}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.js new file mode 100644 index 0000000000..891589bc98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.js @@ -0,0 +1,15 @@ +import {Stringify} from 'shared-runtime'; + +function Repro(props) { + const MY_CONST = -2; + return {props.arg - MY_CONST}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Repro, + params: [ + { + arg: 3, + }, + ], +}; diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 0cadf30bf0..7241ed5149 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -242,6 +242,7 @@ export async function transformFixtureInput( filename: virtualFilepath, highlightCode: false, retainLines: true, + compact: true, plugins: [ [plugin, options], 'babel-plugin-fbt', From 8faebc7a94c9fbc7e055164f931a3f0aab85624c Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 6 Aug 2025 16:02:54 -0400 Subject: [PATCH 417/422] [compiler] Aggregate error reporting in a registry All error messages that are not invariants, todos, or config validation errors are now moved to a registry. This helps us check that error messages and linked documentation is uniform and up to date. This also lets us link error codes to linter categories, breaking the single `react-compiler` rule up into many smaller rules. Changes to error reporting: - instead of writing raw error message into your code, add a new ErrorCode to `CompilerErrorCodes` - create errors by constructing a `CompilerErrorDetailOptions` with the correct error code, or calling `CompilerErrorDetail.fromCode()` / `CompilerDiagnostic.fromCode()`. Changes to linter: - `react-compiler` lint rule is now broken up into many smaller lint rules, each with their own test cases. - linting no longer fails early when a non-fatal error is find. Instead, we now log and move on to lint for more errors! --- .../src/CompilerError.ts | 267 ++++-- .../src/Entrypoint/Imports.ts | 4 +- .../src/Entrypoint/Options.ts | 9 +- .../src/Entrypoint/Pipeline.ts | 36 +- .../src/Entrypoint/Program.ts | 47 +- .../src/Entrypoint/Suppression.ts | 14 +- .../ValidateNoUntransformedReferences.ts | 33 +- .../src/HIR/BuildHIR.ts | 194 ++-- .../src/HIR/Environment.ts | 10 +- .../src/HIR/HIR.ts | 15 +- .../src/HIR/HIRBuilder.ts | 27 +- .../src/Inference/DropManualMemoization.ts | 77 +- .../src/Inference/InferFunctionEffects.ts | 36 +- .../Inference/InferMutationAliasingEffects.ts | 65 +- .../src/Inference/InferReferenceEffects.ts | 16 +- .../src/Transform/TransformFire.ts | 27 +- .../src/Utils/CompilerErrorCodes.ts | 611 ++++++++++++ .../src/Utils/CompilerErrorSeverity.ts | 34 + .../src/Validation/ValidateHooksUsage.ts | 58 +- .../ValidateLocalsNotReassignedAfterRender.ts | 19 +- .../ValidateMemoizedEffectDependencies.ts | 9 +- .../Validation/ValidateNoCapitalizedCalls.ts | 14 +- .../ValidateNoDerivedComputationsInEffects.ts | 21 +- ...ValidateNoFreezingKnownMutableFunctions.ts | 7 +- .../ValidateNoImpureFunctionsInRender.ts | 19 +- .../Validation/ValidateNoJSXInTryStatement.ts | 9 +- .../Validation/ValidateNoRefAccessInRender.ts | 101 +- .../Validation/ValidateNoSetStateInEffects.ts | 23 +- .../Validation/ValidateNoSetStateInRender.ts | 31 +- .../ValidatePreservedManualMemoization.ts | 41 +- .../Validation/ValidateStaticComponents.ts | 11 +- .../src/Validation/ValidateUseMemo.ts | 28 +- ...global-in-component-tag-function.expect.md | 4 +- ...or.assign-global-in-jsx-children.expect.md | 4 +- ...n-global-in-jsx-spread-attribute.expect.md | 6 +- ...rror.bailout-on-flow-suppression.expect.md | 2 +- ...ut-on-suppression-of-custom-rule.expect.md | 4 +- ...ext-variable-only-chained-assign.expect.md | 2 +- ...variable-in-function-declaration.expect.md | 2 +- ...erences-variable-its-assigned-to.expect.md | 2 +- ...destructure-assignment-to-global.expect.md | 4 +- ...ucture-to-local-global-variables.expect.md | 4 +- ...lid-global-reassignment-indirect.expect.md | 4 +- .../error.invalid-hoisting-setstate.expect.md | 4 +- ...valid-impure-functions-in-render.expect.md | 18 +- ...n-of-possible-props-phi-indirect.expect.md | 2 +- ...eassign-local-variable-in-effect.expect.md | 2 +- .../error.invalid-reassign-const.expect.md | 4 +- ...ssign-local-in-hook-return-value.expect.md | 2 +- ...eassign-local-variable-in-effect.expect.md | 2 +- ...-local-variable-in-hook-argument.expect.md | 2 +- ...n-local-variable-in-jsx-callback.expect.md | 2 +- ....invalid-sketchy-code-use-forget.expect.md | 4 +- ...alid-unclosed-eslint-suppression.expect.md | 2 +- ...nconditional-set-state-in-render.expect.md | 4 +- ...ange-shared-inner-outer-function.expect.md | 2 +- ...rror.mutate-property-from-global.expect.md | 2 +- ...or.not-useEffect-external-mutate.expect.md | 4 +- ...r.object-capture-global-mutation.expect.md | 6 +- .../error.reassign-global-fn-arg.expect.md | 4 +- ....reassignment-to-global-indirect.expect.md | 8 +- .../error.reassignment-to-global.expect.md | 8 +- ...ror.sketchy-code-exhaustive-deps.expect.md | 2 +- ...rror.sketchy-code-rules-of-hooks.expect.md | 2 +- .../error.store-property-in-global.expect.md | 2 +- ...ences-later-variable-declaration.expect.md | 2 +- ...state-in-render-after-loop-break.expect.md | 2 +- ...l-set-state-in-render-after-loop.expect.md | 2 +- ...-state-in-render-with-loop-throw.expect.md | 2 +- ...r.unconditional-set-state-lambda.expect.md | 2 +- ...tate-nested-function-expressions.expect.md | 2 +- ...ror.update-global-should-bailout.expect.md | 4 +- ...ror.useMemo-non-literal-depslist.expect.md | 4 +- .../dynamic-gating-invalid-multiple.expect.md | 2 +- .../no-emit/retry-no-emit.expect.md | 5 +- ...setState-in-useEffect-transitive.expect.md | 2 +- .../invalid-setState-in-useEffect.expect.md | 2 +- ...valid-impure-functions-in-render.expect.md | 18 +- ...n-local-variable-in-jsx-callback.expect.md | 2 +- ...rozen-hoisted-storecontext-const.expect.md | 4 +- ...or.not-useEffect-external-mutate.expect.md | 4 +- ....reassignment-to-global-indirect.expect.md | 8 +- .../error.reassignment-to-global.expect.md | 8 +- .../new-mutability/retry-no-emit.expect.md | 5 +- ....validate-useMemo-named-function.expect.md | 2 - .../bailout-retry/error.todo-syntax.expect.md | 2 +- ...ror.untransformed-fire-reference.expect.md | 2 +- .../bailout-retry/error.use-no-memo.expect.md | 2 +- .../error.invalid-outside-effect.expect.md | 4 +- ...id-rewrite-deps-no-array-literal.expect.md | 2 +- ...rror.invalid-rewrite-deps-spread.expect.md | 2 +- .../__tests__/ImpureFunctionCallsRule-test.ts | 32 + .../__tests__/InvalidHooksRule-test.ts | 85 ++ .../__tests__/NoAmbiguousJsxRule-test.ts | 31 + .../__tests__/NoCapitalizedCallsRule-test.ts | 58 ++ .../__tests__/NoRefAccessInRender-tests.ts | 27 + .../__tests__/NoSetStateInEffects-tests.ts | 48 + .../__tests__/NoSetStateInRender-test.ts | 58 ++ .../__tests__/NoUnusedDirectivesRule-test.ts | 58 ++ .../__tests__/PluginTest-test.ts | 172 ++++ .../__tests__/ReactCompilerRule-test.ts | 287 ------ .../ReactCompilerRuleTypescript-test.ts | 24 +- .../__tests__/StaticComponentsRule-test.ts | 44 + .../__tests__/UnnecessaryEffects-tests.ts | 36 + .../__tests__/ValidateUseMemo-test.ts | 43 + .../__tests__/shared-utils.ts | 98 ++ .../eslint-plugin-react-compiler/package.json | 4 +- .../eslint-plugin-react-compiler/src/index.ts | 52 +- .../src/rules/ReactCompilerRule.ts | 626 ++++++------ .../src/shared/RunReactCompiler.ts | 323 +++++++ compiler/packages/snap/src/compiler.ts | 11 +- compiler/yarn.lock | 901 +++++++++++++++++- 112 files changed, 3742 insertions(+), 1411 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts delete mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index ee0d0f74c5..6bce5dca2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -10,48 +10,22 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {SourceLocation} from './HIR'; import {Err, Ok, Result} from './Utils/Result'; import {assertExhaustive} from './Utils/utils'; - -export enum ErrorSeverity { - /** - * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some - * misunderstanding on the user’s part. - */ - InvalidJS = 'InvalidJS', - /** - * JS syntax that is not supported and which we do not plan to support. Developers should - * rewrite to use supported forms. - */ - UnsupportedJS = 'UnsupportedJS', - /** - * Code that breaks the rules of React. - */ - InvalidReact = 'InvalidReact', - /** - * Incorrect configuration of the compiler. - */ - InvalidConfig = 'InvalidConfig', - /** - * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve - * memoization. - */ - CannotPreserveMemoization = 'CannotPreserveMemoization', - /** - * Unhandled syntax that we don't support yet. - */ - Todo = 'Todo', - /** - * An unexpected internal error in the compiler that indicates critical issues that can panic - * the compiler. - */ - Invariant = 'Invariant', -} +import {ErrorSeverity} from './Utils/CompilerErrorSeverity'; +import { + ErrorCode, + ErrorCodeDetails, + LinterCategory, +} from './Utils/CompilerErrorCodes'; +export {ErrorSeverity}; +export {ErrorCode, ErrorCodeDetails, LinterCategory}; export type CompilerDiagnosticOptions = { severity: ErrorSeverity; category: string; - description: string; + description?: string | null | undefined; details: Array; suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; }; export type CompilerDiagnosticDetail = @@ -86,13 +60,28 @@ export type CompilerSuggestion = description: string; }; -export type CompilerErrorDetailOptions = { +export type PlainCompilerErrorDetailOptions = { + errorCode?: void; reason: string; description?: string | null | undefined; - severity: ErrorSeverity; + severity: + | ErrorSeverity.Invariant + | ErrorSeverity.Todo + | ErrorSeverity.InvalidConfig; loc: SourceLocation | null; suggestions?: Array | null | undefined; }; +export type CodedCompilerErrorDetailOptions = { + errorCode: ErrorCode; + description?: string | null | undefined; + loc: SourceLocation | null; + suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; +}; + +export type CompilerErrorDetailOptions = + | PlainCompilerErrorDetailOptions + | CodedCompilerErrorDetailOptions; export type PrintErrorMessageOptions = { /** @@ -102,19 +91,72 @@ export type PrintErrorMessageOptions = { eslint: boolean; }; +export function makeCompilerDiagnostic( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + }, +): CompilerDiagnostic { + return makeCompilerDiagnostic(code, options); +} + export class CompilerDiagnostic { options: CompilerDiagnosticOptions; - constructor(options: CompilerDiagnosticOptions) { + /** + * Constructor is private to enforce that we either only create invariant diagnostics + * or use ErrorCodes + */ + private constructor(options: CompilerDiagnosticOptions) { this.options = options; } - static create( - options: Omit, - ): CompilerDiagnostic { + static create< + T extends CompilerDiagnosticOptions & {severity: ErrorSeverity.Invariant}, + >(options: Omit): CompilerDiagnostic { return new CompilerDiagnostic({...options, details: []}); } + static fromCode( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + details?: Array | null | undefined; + }, + ): CompilerDiagnostic { + const errorEntry = ErrorCodeDetails[code]; + let description = undefined; + if (errorEntry.description != null) { + description = errorEntry.description; + } + if (options?.description != null && options.description.length > 0) { + if (description != null && description.length > 0) { + description += ' '; + } else { + description = ''; + } + description += options.description; + } + + const diagnosticOptions: CompilerDiagnosticOptions = { + severity: errorEntry.severity, + category: errorEntry.reason, + description, + linterCategory: errorEntry.linterCategory, + suggestions: options?.suggestions, + details: options?.details ?? [], + }; + + return new CompilerDiagnostic(diagnosticOptions); + } + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return {options: {...this.options, linterCategory: undefined}}; + } + get category(): CompilerDiagnosticOptions['category'] { return this.options.category; } @@ -127,6 +169,9 @@ export class CompilerDiagnostic { get suggestions(): CompilerDiagnosticOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): CompilerDiagnosticOptions['linterCategory'] { + return this.options.linterCategory; + } withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic { this.options.details.push(detail); @@ -138,11 +183,10 @@ export class CompilerDiagnostic { } printErrorMessage(source: string, options: PrintErrorMessageOptions): string { - const buffer = [ - printErrorSummary(this.severity, this.category), - '\n\n', - this.description, - ]; + const buffer = [printErrorSummary(this.severity, this.category)]; + if (this.description != null) { + buffer.push(`\n\n${this.description}`); + } for (const detail of this.options.details) { switch (detail.kind) { case 'error': { @@ -202,13 +246,65 @@ export class CompilerErrorDetail { this.options = options; } - get reason(): CompilerErrorDetailOptions['reason'] { - return this.options.reason; + static fromCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + return new CompilerErrorDetail({ + ...details, + errorCode: code, + } as CodedCompilerErrorDetailOptions); } - get description(): CompilerErrorDetailOptions['description'] { + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return { + options: { + reason: this.reason, + description: this.description, + severity: this.severity, + loc: this.loc, + suggestions: this.suggestions, + }, + }; + } + + get reason(): string { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].reason; + } else { + return this.options.reason; + } + } + get description(): string | null | undefined { + if (this.options.errorCode != null) { + let description = undefined; + if (ErrorCodeDetails[this.options.errorCode].description != null) { + description = ErrorCodeDetails[this.options.errorCode].description; + } + if ( + this.options.description != null && + this.options.description.length > 0 + ) { + if (description != null && description.length > 0) { + description += '. '; + } else { + description = ''; + } + description += this.options.description; + } + return description; + } return this.options.description; } - get severity(): CompilerErrorDetailOptions['severity'] { + get severity(): ErrorSeverity { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].severity; + } return this.options.severity; } get loc(): CompilerErrorDetailOptions['loc'] { @@ -217,6 +313,12 @@ export class CompilerErrorDetail { get suggestions(): CompilerErrorDetailOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): LinterCategory | null | undefined { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].linterCategory; + } + return undefined; + } primaryLocation(): SourceLocation | null { return this.loc; @@ -266,7 +368,7 @@ export class CompilerError extends Error { static invariant( condition: unknown, - options: Omit, + options: Omit, ): asserts condition { if (!condition) { const errors = new CompilerError(); @@ -280,14 +382,14 @@ export class CompilerError extends Error { } } - static throwDiagnostic(options: CompilerDiagnosticOptions): never { + static throwDiagnostic(diagnostic: CompilerDiagnostic): never { const errors = new CompilerError(); - errors.pushDiagnostic(new CompilerDiagnostic(options)); + errors.pushDiagnostic(diagnostic); throw errors; } static throwTodo( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -296,34 +398,21 @@ export class CompilerError extends Error { throw errors; } - static throwInvalidJS( - options: Omit, + static throwFromCode( + code: ErrorCode, + options?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, ): never { const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidJS, - }), - ); - throw errors; - } - - static throwInvalidReact( - options: Omit, - ): never { - const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidReact, - }), - ); + errors.pushErrorDetail(CompilerErrorDetail.fromCode(code, options)); throw errors; } static throwInvalidConfig( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -392,13 +481,10 @@ export class CompilerError extends Error { } push(options: CompilerErrorDetailOptions): CompilerErrorDetail { - const detail = new CompilerErrorDetail({ - reason: options.reason, - description: options.description ?? null, - severity: options.severity, - suggestions: options.suggestions, - loc: typeof options.loc === 'symbol' ? null : options.loc, - }); + if (options instanceof CompilerErrorDetail) { + return this.pushErrorDetail(options); + } + const detail = new CompilerErrorDetail(options); return this.pushErrorDetail(detail); } @@ -407,6 +493,19 @@ export class CompilerError extends Error { return detail; } + pushErrorCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + const detail = CompilerErrorDetail.fromCode(code, details); + this.details.push(detail); + return detail; + } + hasErrors(): boolean { return this.details.length > 0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 24ce37cf72..ea1d28a62e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -38,10 +38,10 @@ export function validateRestrictedImports( ImportDeclaration(importDeclPath) { if (restrictedImports.has(importDeclPath.node.source.value)) { error.push({ - severity: ErrorSeverity.Todo, reason: 'Bailing out due to blocklisted import', description: `Import from module ${importDeclPath.node.source.value}`, loc: importDeclPath.node.loc ?? null, + severity: ErrorSeverity.Todo, }); } }, @@ -205,10 +205,10 @@ export class ProgramContext { } const error = new CompilerError(); error.push({ - severity: ErrorSeverity.Todo, reason: 'Encountered conflicting global in generated program', description: `Conflict from local binding ${name}`, loc: scope.getBinding(name)?.path.node.loc ?? null, + severity: ErrorSeverity.Todo, suggestions: null, }); return Err(error); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c13940ed10..1a8e1457ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerErrorDetail, - CompilerErrorDetailOptions, + PlainCompilerErrorDetailOptions, } from '../CompilerError'; import { EnvironmentConfig, @@ -107,6 +107,8 @@ export type PluginOptions = { * passes. * * Defaults to false + * + * TODO: rename this to lintOnly or something similar */ noEmit: boolean; @@ -234,7 +236,10 @@ export type CompileErrorEvent = { export type CompileDiagnosticEvent = { kind: 'CompileDiagnostic'; fnLoc: t.SourceLocation | null; - detail: Omit, 'suggestions'>; + detail: Omit< + Omit, + 'suggestions' + >; }; export type CompileSuccessEvent = { kind: 'CompileSuccess'; 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 a4f984f195..7a004fa18c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -100,7 +100,6 @@ import {outlineJSX} from '../Optimization/OutlineJsx'; import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls'; import {transformFire} from '../Transform'; import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender'; -import {CompilerError} from '..'; import {validateStaticComponents} from '../Validation/ValidateStaticComponents'; import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions'; import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects'; @@ -174,7 +173,7 @@ function runWithEnvironment( !env.config.disableMemoizationForDebugging && !env.config.enableChangeDetectionForDebugging ) { - dropManualMemoization(hir).unwrap(); + env.logOrThrowErrors(dropManualMemoization(hir)); log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } @@ -207,10 +206,10 @@ function runWithEnvironment( if (env.isInferredMemoEnabled) { if (env.config.validateHooksUsage) { - validateHooksUsage(hir).unwrap(); + env.logOrThrowErrors(validateHooksUsage(hir)); } - if (env.config.validateNoCapitalizedCalls) { - validateNoCapitalizedCalls(hir).unwrap(); + if (env.config.validateNoCapitalizedCalls != null) { + env.logOrThrowErrors(validateNoCapitalizedCalls(hir)); } } @@ -230,20 +229,17 @@ function runWithEnvironment( log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); if (!env.config.enableNewMutationAliasingModel) { - const fnEffectErrors = inferReferenceEffects(hir); + const fnEffectResult = inferReferenceEffects(hir); + if (env.isInferredMemoEnabled) { - if (fnEffectErrors.length > 0) { - CompilerError.throw(fnEffectErrors[0]); - } + env.logOrThrowErrors(fnEffectResult); } log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); } else { const mutabilityAliasingErrors = inferMutationAliasingEffects(hir); log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } + env.logOrThrowErrors(mutabilityAliasingErrors); } } @@ -272,10 +268,8 @@ function runWithEnvironment( }); log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } - validateLocalsNotReassignedAfterRender(hir); + env.logOrThrowErrors(mutabilityAliasingErrors.map(() => undefined)); + env.logOrThrowErrors(validateLocalsNotReassignedAfterRender(hir)); } } @@ -285,15 +279,15 @@ function runWithEnvironment( } if (env.config.validateRefAccessDuringRender) { - validateNoRefAccessInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoRefAccessInRender(hir)); } if (env.config.validateNoSetStateInRender) { - validateNoSetStateInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoSetStateInRender(hir)); } if (env.config.validateNoDerivedComputationsInEffects) { - validateNoDerivedComputationsInEffects(hir); + env.logOrThrowErrors(validateNoDerivedComputationsInEffects(hir)); } if (env.config.validateNoSetStateInEffects) { @@ -305,14 +299,14 @@ function runWithEnvironment( } if (env.config.validateNoImpureFunctionsInRender) { - validateNoImpureFunctionsInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoImpureFunctionsInRender(hir)); } if ( env.config.validateNoFreezingKnownMutableFunctions || env.config.enableNewMutationAliasingModel ) { - validateNoFreezingKnownMutableFunctions(hir).unwrap(); + env.logOrThrowErrors(validateNoFreezingKnownMutableFunctions(hir)); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 79bbee37a5..825f83a2a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -7,11 +7,7 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, ErrorSeverity} from '../CompilerError'; import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; @@ -32,6 +28,7 @@ import { } from './Suppression'; import {GeneratedSource} from '../HIR'; import {Err, Ok, Result} from '../Utils/Result'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; export type CompilerPass = { opts: PluginOptions; @@ -101,12 +98,9 @@ function findDirectivesDynamicGating( if (t.isValidIdentifier(maybeMatch[1])) { result.push({directive, match: maybeMatch[1]}); } else { - errors.push({ - reason: `Dynamic gating directive is not a valid JavaScript identifier`, + errors.pushErrorCode(ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, { description: `Found '${directive.value.value}'`, - severity: ErrorSeverity.InvalidReact, loc: directive.loc ?? null, - suggestions: null, }); } } @@ -115,14 +109,11 @@ function findDirectivesDynamicGating( return Err(errors); } else if (result.length > 1) { const error = new CompilerError(); - error.push({ - reason: `Multiple dynamic gating directives found`, + error.pushErrorCode(ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, { description: `Expected a single directive but found [${result .map(r => r.directive.value.value) .join(', ')}]`, - severity: ErrorSeverity.InvalidReact, loc: result[0].directive.loc ?? null, - suggestions: null, }); return Err(error); } else if (result.length === 1) { @@ -451,14 +442,12 @@ export function compileProgram( if (programContext.hasModuleScopeOptOut) { if (compiledFns.length > 0) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: - 'Unexpected compiled functions when module scope opt-out is present', - severity: ErrorSeverity.Invariant, - loc: null, - }), - ); + error.push({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }); handleError(error, programContext, null); } return null; @@ -591,9 +580,7 @@ function processFn( let compiledFn: CodegenFunction; const compileResult = tryCompileFunction(fn, fnType, programContext); if (compileResult.kind === 'error') { - if (directives.optOut != null) { - logError(compileResult.error, programContext, fn.node.loc ?? null); - } else { + if (directives.optOut == null) { handleError(compileResult.error, programContext, fn.node.loc ?? null); } const retryResult = retryCompileFunction(fn, fnType, programContext); @@ -692,7 +679,7 @@ function tryCompileFunction( fn, programContext.opts.environment, fnType, - 'all_features', + programContext.opts.noEmit ? 'lint_only' : 'all_features', programContext, programContext.opts.logger, programContext.filename, @@ -805,15 +792,7 @@ function shouldSkipCompilation( if (pass.opts.sources) { if (pass.filename === null) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: `Expected a filename but found none.`, - description: - "When the 'sources' config options is specified, the React compiler will only compile files with a name", - severity: ErrorSeverity.InvalidConfig, - loc: null, - }), - ); + error.pushErrorCode(ErrorCode.FILENAME_NOT_SET); handleError(error, pass, null); return true; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts index ee341b111f..75828aa108 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, - ErrorSeverity, + ErrorCode, } from '../CompilerError'; import {assertExhaustive} from '../Utils/utils'; import {GeneratedSource} from '../HIR'; @@ -165,14 +165,12 @@ export function suppressionsToCompilerError( let reason, suggestion; switch (suppressionRange.source) { case 'Eslint': - reason = - 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled'; + reason = ErrorCode.BAILOUT_ESLINT_SUPPRESSION; suggestion = 'Remove the ESLint suppression and address the React error'; break; case 'Flow': - reason = - 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow'; + reason = ErrorCode.BAILOUT_FLOW_SUPPRESSION; suggestion = 'Remove the Flow suppression and address the React error'; break; default: @@ -182,10 +180,8 @@ export function suppressionsToCompilerError( ); } error.pushDiagnostic( - CompilerDiagnostic.create({ - category: reason, - description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``, - severity: ErrorSeverity.InvalidReact, + CompilerDiagnostic.fromCode(reason, { + description: `Found suppression \`${suppressionRange.disableComment.value.trim()}\``, suggestions: [ { description: suggestion, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 16d7c3713c..5271d66886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -8,27 +8,24 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..'; +import {CompilerError, EnvironmentConfig, Logger} from '..'; import {getOrInsertWith} from '../Utils/utils'; import {Environment, GeneratedSource} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; import {CompileProgramMetadata} from './Program'; -import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError'; +import {CompilerDiagnostic} from '../CompilerError'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; -function throwInvalidReact( - options: Omit, +function logAndThrowDiagnostic( + diagnostic: CompilerDiagnostic, {logger, filename}: TraversalState, ): never { - const detail: CompilerDiagnosticOptions = { - severity: ErrorSeverity.InvalidReact, - ...options, - }; logger?.logEvent(filename, { kind: 'CompileError', fnLoc: null, - detail: new CompilerDiagnostic(detail), + detail: diagnostic, }); - CompilerError.throwDiagnostic(detail); + CompilerError.throwDiagnostic(diagnostic); } function isAutodepsSigil( @@ -90,10 +87,8 @@ function assertValidEffectImportReference( * as it may have already been transformed by the compiler (and not * memoized). */ - throwInvalidReact( - { - category: - 'Cannot infer dependencies of this effect. This will break your build!', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.DID_NOT_INFER_DEPS, { description: 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' + (maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''), @@ -104,7 +99,7 @@ function assertValidEffectImportReference( loc: parent.node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } @@ -121,10 +116,8 @@ function assertValidFireImportReference( paths[0], context.transformErrors, ); - throwInvalidReact( - { - category: - '[Fire] Untransformed reference to compiler-required feature.', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.CANNOT_COMPILE_FIRE, { description: 'Either remove this `fire` call or ensure it is successfully transformed by the compiler' + maybeErrorDiagnostic @@ -137,7 +130,7 @@ function assertValidFireImportReference( loc: paths[0].node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } 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 3b11670146..26f05f58ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -12,6 +12,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, + ErrorCode, ErrorSeverity, } from '../CompilerError'; import {Err, Ok, Result} from '../Utils/Result'; @@ -170,14 +171,12 @@ export function lower( ); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.Todo, - category: `Handle ${param.node.type} parameters`, + CompilerDiagnostic.fromCode(ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, { description: `[BuildHIR] Add support for ${param.node.type} parameters.`, }).withDetail({ kind: 'error', loc: param.node.loc ?? null, - message: 'Unsupported parameter type', + message: `Unsupported parameter type: ${param.node.type}`, }), ); } @@ -201,14 +200,12 @@ export function lower( directives = body.get('directives').map(d => d.node.value.value); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidJS, - category: `Unexpected function body kind`, + CompilerDiagnostic.fromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`, }).withDetail({ kind: 'error', loc: body.node.loc ?? null, - message: 'Expected a block statement or expression', + message: 'Change this to a block statement or expression', }), ); } @@ -769,12 +766,12 @@ function lowerStatement( const testExpr = case_.get('test'); if (testExpr.node == null) { if (hasDefault) { - builder.errors.push({ - reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, - severity: ErrorSeverity.InvalidJS, - loc: case_.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + { + loc: case_.node.loc ?? null, + }, + ); break; } hasDefault = true; @@ -886,19 +883,20 @@ function lowerStatement( if (builder.isContextIdentifier(id)) { if (kind === InstructionKind.Const) { const declRangeStart = declaration.parentPath.node.start!; - builder.errors.push({ - reason: `Expect \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: id.node.loc ?? null, - suggestions: [ - { - description: 'Change to a `let` declaration', - op: CompilerSuggestionOperation.Replace, - range: [declRangeStart, declRangeStart + 5], // "const".length - text: 'let', - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: id.node.loc ?? null, + suggestions: [ + { + description: 'Change to a `let` declaration', + op: CompilerSuggestionOperation.Replace, + range: [declRangeStart, declRangeStart + 5], // "const".length + text: 'let', + }, + ], + }, + ); } lowerValueToTemporary(builder, { kind: 'DeclareContext', @@ -932,13 +930,13 @@ function lowerStatement( } } } else { - builder.errors.push({ - reason: `Expected variable declaration to be an identifier if no initializer was provided`, - description: `Got a \`${id.type}\``, - severity: ErrorSeverity.InvalidJS, - loc: stmt.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + { + description: `Got a \`${id.type}\``, + loc: stmt.node.loc ?? null, + }, + ); } } return; @@ -1374,12 +1372,8 @@ function lowerStatement( return; } case 'WithStatement': { - builder.errors.push({ - reason: `JavaScript 'with' syntax is not supported`, - description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_WITH, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1394,12 +1388,8 @@ function lowerStatement( * and complex enough to support that we don't anticipate supporting anytime soon. Developers * are encouraged to lift classes out of component/hook declarations. */ - builder.errors.push({ - reason: 'Inline `class` declarations are not supported', - description: `Move class declarations outside of components/hooks`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_INNER_CLASS, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1423,12 +1413,8 @@ function lowerStatement( case 'ImportDeclaration': case 'TSExportAssignment': case 'TSImportEqualsDeclaration': { - builder.errors.push({ - reason: - 'JavaScript `import` and `export` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_IMPORT_EXPORT, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1438,12 +1424,8 @@ function lowerStatement( return; } case 'TSNamespaceExportDeclaration': { - builder.errors.push({ - reason: - 'TypeScript `namespace` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_TS_NAMESPACE, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1698,12 +1680,8 @@ function lowerExpression( const expr = exprPath as NodePath; const calleePath = expr.get('callee'); if (!calleePath.isExpression()) { - builder.errors.push({ - reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, - description: `Got a \`${calleePath.node.type}\``, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_NEW_EXPRESSION, { loc: calleePath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -1800,12 +1778,12 @@ function lowerExpression( last = lowerExpressionToTemporary(builder, item); } if (last === null) { - builder.errors.push({ - reason: `Expected sequence expression to have at least one expression`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + { + loc: expr.node.loc ?? null, + }, + ); } else { lowerValueToTemporary(builder, { kind: 'StoreLocal', @@ -2289,18 +2267,18 @@ function lowerExpression( }); for (const [name, locations] of Object.entries(fbtLocations)) { if (locations.length > 1) { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support duplicate fbt tags', - description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, - details: locations.map(loc => { - return { - kind: 'error', - message: `Multiple \`<${tagName}:${name}>\` tags found`, - loc, - }; + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_DUPLICATE_FBT_TAGS, { + description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, + details: locations.map(loc => { + return { + kind: 'error', + message: `Multiple \`<${tagName}:${name}>\` tags found`, + loc, + }; + }), }), - }); + ); } } } @@ -2389,11 +2367,8 @@ function lowerExpression( const quasis = expr.get('quasis'); if (subexprs.length !== quasis.length - 1) { - builder.errors.push({ - reason: `Unexpected quasi and subexpression lengths in template literal`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_QUASI_LENGTHS, { loc: exprPath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -2441,24 +2416,23 @@ function lowerExpression( }; } } else { - builder.errors.push({ - reason: `Only object properties can be deleted`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: [ - { - description: 'Remove this line', - range: [expr.node.start!, expr.node.end!], - op: CompilerSuggestionOperation.Remove, - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + { + loc: expr.node.loc ?? null, + suggestions: [ + { + description: 'Remove this line', + range: [expr.node.start!, expr.node.end!], + op: CompilerSuggestionOperation.Remove, + }, + ], + }, + ); return {kind: 'UnsupportedNode', node: expr.node, loc: exprLoc}; } } else if (expr.node.operator === 'throw') { - builder.errors.push({ - reason: `Throw expressions are not supported`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_THROW_EXPRESSION, { loc: expr.node.loc ?? null, suggestions: [ { @@ -3285,10 +3259,8 @@ function lowerJsxElementName( const name = exprPath.node.name.name; const tag = `${namespace}:${name}`; if (namespace.indexOf(':') !== -1 || name.indexOf(':') !== -1) { - builder.errors.push({ - reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + builder.errors.pushErrorCode(ErrorCode.INVALID_JSX_NAMESPACED_NAME, { description: `Got \`${namespace}\` : \`${name}\``, - severity: ErrorSeverity.InvalidJS, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3583,11 +3555,7 @@ function lowerIdentifier( } default: { if (binding.kind === 'Global' && binding.name === 'eval') { - builder.errors.push({ - reason: `The 'eval' function is not supported`, - description: - 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_EVAL, { loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3653,9 +3621,7 @@ function lowerIdentifierForAssignment( binding.bindingKind === 'const' && kind === InstructionKind.Reassign ) { - builder.errors.push({ - reason: `Cannot reassign a \`const\` variable`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, { loc: path.node.loc ?? null, description: binding.identifier.name != null @@ -3710,12 +3676,13 @@ function lowerAssignment( let temporary; if (builder.isContextIdentifier(lvalue)) { if (kind === InstructionKind.Const && !isHoistedIdentifier) { - builder.errors.push({ - reason: `Expected \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: lvalue.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: lvalue.node.loc ?? null, + suggestions: null, + }, + ); } if ( @@ -3726,7 +3693,8 @@ function lowerAssignment( ) { builder.errors.push({ reason: `Unexpected context variable kind`, - severity: ErrorSeverity.InvalidJS, + description: `Expected one of Const, Reassign, Let, Function, got ${kind}`, + severity: ErrorSeverity.Invariant, loc: lvalue.node.loc ?? null, suggestions: null, }); 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 0ac59a8982..5541c86559 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -92,7 +92,7 @@ export const MacroSchema = z.union([ z.tuple([z.string(), z.array(MacroMethodSchema)]), ]); -export type CompilerMode = 'all_features' | 'no_inferred_memo'; +export type CompilerMode = 'all_features' | 'no_inferred_memo' | 'lint_only'; export type Macro = z.infer; export type MacroMethod = z.infer; @@ -800,6 +800,14 @@ export class Environment { } } + logOrThrowErrors(errors: Result): void { + if (this.compilerMode === 'lint_only') { + this.logErrors(errors); + } else { + errors.unwrap(); + } + } + isContextIdentifier(node: t.Identifier): boolean { return this.#contextIdentifiers.has(node); } 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 51715b3c1e..89b2c0fc41 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -15,6 +15,7 @@ import {Type, makeType} from './Types'; import {z} from 'zod'; import type {AliasingEffect} from '../Inference/AliasingEffects'; import {isReservedWord} from '../Utils/Keyword'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -1322,18 +1323,18 @@ export function forkTemporaryIdentifier( */ export function makeIdentifierName(name: string): ValidatedIdentifier { if (isReservedWord(name)) { - CompilerError.throwInvalidJS({ - reason: 'Expected a non-reserved identifier name', - loc: GeneratedSource, - description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, - suggestions: null, - }); + CompilerError.throwFromCode( + ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + { + loc: GeneratedSource, + description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, + }, + ); } else { CompilerError.invariant(t.isValidIdentifier(name), { reason: `Expected a valid identifier name`, loc: GeneratedSource, description: `\`${name}\` is not a valid JavaScript identifier`, - suggestions: null, }); } return { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 81959ea361..1d71452d0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -7,7 +7,7 @@ import {Binding, NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {Environment} from './Environment'; import { BasicBlock, @@ -37,6 +37,7 @@ import { mapTerminalSuccessors, terminalFallthrough, } from './visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -308,19 +309,17 @@ export default class HIRBuilder { resolveBinding(node: t.Identifier): Identifier { if (node.name === 'fbt') { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support local variables named `fbt`', - description: - 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', - details: [ - { - kind: 'error', - message: 'Rename to avoid conflict with fbt plugin', - loc: node.loc ?? GeneratedSource, - }, - ], - }); + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, { + details: [ + { + kind: 'error', + message: 'Rename to avoid conflict with fbt plugin', + loc: node.loc ?? GeneratedSource, + }, + ], + }), + ); } const originalName = node.name; let name = originalName; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 7aeb3edb22..fa9bddd3f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -5,12 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, - SourceLocation, -} from '..'; +import {CompilerDiagnostic, CompilerError, SourceLocation} from '..'; import { CallExpression, Effect, @@ -35,6 +30,7 @@ import { makeInstructionId, } from '../HIR'; import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; type ManualMemoCallee = { @@ -299,12 +295,11 @@ function extractManualMemoizationArgs( >; if (fnPlace == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected a callback function to be passed to ${kind}`, - description: `Expected a callback function to be passed to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + kind === 'useMemo' + ? ErrorCode.INVALID_USE_MEMO_NO_ARG0 + : ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Expected a callback function to be passed to ${kind}`, @@ -314,12 +309,11 @@ function extractManualMemoizationArgs( } if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Unexpected spread argument to ${kind}`, - description: `Unexpected spread argument to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + fnPlace.kind === 'Spread' + ? ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT + : ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Unexpected spread argument to ${kind}`, @@ -334,12 +328,9 @@ function extractManualMemoizationArgs( ); if (maybeDepsList == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list for ${kind} to be an array literal`, - description: `Expected the dependency list for ${kind} to be an array literal`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + ).withDetail({ kind: 'error', loc: depsListPlace.loc, message: `Expected the dependency list for ${kind} to be an array literal`, @@ -352,12 +343,9 @@ function extractManualMemoizationArgs( const maybeDep = sidemap.maybeDeps.get(dep.identifier.id); if (maybeDep == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + ).withDetail({ kind: 'error', loc: dep.loc, message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, @@ -457,16 +445,16 @@ export function dropManualMemoization( if (funcToCheck !== undefined && funcToCheck.loweredFunc.func) { if (!hasNonVoidReturn(funcToCheck.loweredFunc.func)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks must return a value', - description: `This ${ - manualMemo.loadInstr.value.kind === 'PropertyLoad' - ? 'React.useMemo' - : 'useMemo' - } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + { + description: `This ${ + manualMemo.loadInstr.value.kind === 'PropertyLoad' + ? 'React.useMemo' + : 'useMemo' + } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, + }, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: 'useMemo() callbacks must return a value', @@ -497,12 +485,9 @@ export function dropManualMemoization( */ if (!sidemap.functions.has(fnPlace.identifier.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the first argument to be an inline function expression`, - description: `Expected the first argument to be an inline function expression`, - suggestions: [], - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + ).withDetail({ kind: 'error', loc: fnPlace.loc, message: `Expected the first argument to be an inline function expression`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts index a01ca188a0..5fcf8a3cae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts @@ -25,6 +25,7 @@ import { isRefOrRefValue, } from '../HIR'; import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {assertExhaustive} from '../Utils/utils'; interface State { @@ -62,22 +63,20 @@ function inferOperandEffect(state: State, place: Place): null | FunctionEffect { // We ignore mutations of primitives since this is not a React-specific problem value.kind !== ValueKind.Primitive ) { - let reason = getWriteErrorReason(value); + let errorCode = getWriteErrorReason(value); return { kind: value.reason.size === 1 && value.reason.has(ValueReason.Global) ? 'GlobalMutation' : 'ReactMutation', error: { - reason, + errorCode, description: place.identifier.name !== null && place.identifier.name.kind === 'named' ? `Found mutation of \`${place.identifier.name.value}\`` : null, loc: place.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }; } @@ -266,11 +265,8 @@ export function inferInstructionFunctionEffects( functionEffects.push({ kind: 'GlobalMutation', error: { - reason: - 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + errorCode: ErrorCode.INVALID_WRITE_GLOBAL, loc: instr.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }); break; @@ -324,28 +320,28 @@ function isEffectSafeOutsideRender(effect: FunctionEffect): boolean { return effect.kind === 'GlobalMutation'; } -export function getWriteErrorReason(abstractValue: AbstractValue): string { +export function getWriteErrorReason(abstractValue: AbstractValue): ErrorCode { if (abstractValue.reason.has(ValueReason.Global)) { - return 'Modifying a variable defined outside a component or hook is not allowed. Consider using an effect'; + return ErrorCode.INVALID_WRITE_GLOBAL; } else if (abstractValue.reason.has(ValueReason.JsxCaptured)) { - return 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX'; + return ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX; } else if (abstractValue.reason.has(ValueReason.Context)) { - return `Modifying a value returned from 'useContext()' is not allowed.`; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT; } else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) { - return 'Modifying a value returned from a function whose return value should not be mutated'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE; } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) { - return 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS; } else if (abstractValue.reason.has(ValueReason.State)) { - return "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead"; + return ErrorCode.INVALID_WRITE_STATE; } else if (abstractValue.reason.has(ValueReason.ReducerState)) { - return "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead"; + return ErrorCode.INVALID_WRITE_REDUCER_STATE; } else if (abstractValue.reason.has(ValueReason.Effect)) { - return 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()'; + return ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY; } else if (abstractValue.reason.has(ValueReason.HookCaptured)) { - return 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook'; + return ErrorCode.INVALID_WRITE_HOOK_CAPTURED; } else if (abstractValue.reason.has(ValueReason.HookReturn)) { - return 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed'; + return ErrorCode.INVALID_WRITE_HOOK_RETURN; } else { - return 'This modifies a variable that React considers immutable'; + return ErrorCode.INVALID_WRITE_GENERIC; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index 2adf78fe05..4d1eb3d757 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -69,6 +69,7 @@ import {getWriteErrorReason} from './InferFunctionEffects'; import prettyFormat from 'pretty-format'; import {createTemporaryPlace} from '../HIR/HIRBuilder'; import {AliasingEffect, AliasingSignature, hashEffect} from './AliasingEffects'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; const DEBUG = false; @@ -442,7 +443,7 @@ function applySignature( const value = state.kind(effect.value); switch (value.kind) { case ValueKind.Frozen: { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -455,10 +456,9 @@ function applySignature( effects.push({ kind: 'MutateFrozen', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -1026,22 +1026,20 @@ function applyEffect( const hoistedAccess = context.hoistedContextDeclarations.get( effect.value.identifier.declarationId, ); - const diagnostic = CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access variable before it is declared', - description: `${variable ?? 'This variable'} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.`, - }); + const diagnostic = CompilerDiagnostic.fromCode( + ErrorCode.INVALID_ACCESS_BEFORE_INIT, + ); if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) { diagnostic.withDetail({ kind: 'error', loc: hoistedAccess.loc, - message: `${variable ?? 'variable'} accessed before it is declared`, + message: `${variable ?? 'This variable'} is accessed before it is declared`, }); } diagnostic.withDetail({ kind: 'error', loc: effect.value.loc, - message: `${variable ?? 'variable'} is declared here`, + message: `${variable ?? 'This variable'} is declared here`, }); applyEffect( @@ -1056,7 +1054,7 @@ function applyEffect( effects, ); } else { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -1075,10 +1073,9 @@ function applyEffect( ? 'MutateFrozen' : 'MutateGlobal', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -2006,15 +2003,12 @@ function computeSignatureForInstruction( effects.push({ kind: 'MutateGlobal', place: value.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'Cannot reassign variables declared outside of the component/hook', - description: `Variable ${variable} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)`, - }).withDetail({ + error: CompilerDiagnostic.fromCode( + ErrorCode.INVALID_WRITE_GLOBAL, + ).withDetail({ kind: 'error', loc: instr.loc, - message: `${variable} cannot be reassigned`, + message: `${variable} should not be reassigned`, }), }); effects.push({kind: 'Assign', from: value.value, into: lvalue}); @@ -2105,19 +2099,16 @@ function computeEffectsForLegacySignature( effects.push({ kind: 'Impure', place: receiver, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - }).withDetail({ - kind: 'error', - loc, - message: 'Cannot call impure function', - }), + error: CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail( + { + kind: 'error', + loc, + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', + }, + ), }); } const stores: Array = []; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 1b0856791a..a806ebd4bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError'; +import {CompilerError} from '../CompilerError'; import {Environment} from '../HIR'; import { AbstractValue, @@ -48,6 +48,7 @@ import { eachTerminalOperand, eachTerminalSuccessor, } from '../HIR/visitors'; +import {Err, Ok, Result} from '../Utils/Result'; import {assertExhaustive, Set_isSuperset} from '../Utils/utils'; import { inferTerminalFunctionEffects, @@ -106,7 +107,7 @@ const UndefinedValue: InstructionValue = { export default function inferReferenceEffects( fn: HIRFunction, options: {isFunctionExpression: boolean} = {isFunctionExpression: false}, -): Array { +): Result { /* * Initial state contains function params * TODO: include module declarations here as well @@ -247,10 +248,17 @@ export default function inferReferenceEffects( if (options.isFunctionExpression) { fn.effects = functionEffects; - return []; } else { - return transformFunctionEffectErrors(functionEffects); + const errors = transformFunctionEffectErrors(functionEffects); + if (errors.length > 0) { + const compilerError = new CompilerError(); + for (const detail of errors) { + compilerError.push(detail); + } + return Err(compilerError); + } } + return Ok(void 0); } type FreezeAction = {values: Set; reason: Set}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts index f88c85f2f0..dbb84944b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts @@ -42,6 +42,7 @@ import { import {eachInstructionOperand} from '../HIR/visitors'; import {printSourceLocationLine} from '../HIR/PrintHIR'; import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * TODO(jmbrown): @@ -50,8 +51,6 @@ import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; * - React.useEffect calls */ -const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`'; - export function transformFire(fn: HIRFunction): void { const context = new Context(fn.env); replaceFireFunctions(fn, context); @@ -178,9 +177,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } else if (value.args.length > 1 && value.args[1].kind === 'Spread') { @@ -188,9 +185,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].place.loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -243,11 +238,9 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } else { context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description: '`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed', - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else { @@ -262,10 +255,8 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else if (value.kind === 'CallExpression') { @@ -394,9 +385,7 @@ function ensureNoRemainingCalleeCaptures( description: `All uses of ${calleeName} must be either used with a fire() call in \ this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \ ${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -411,9 +400,7 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { context.pushError({ loc: place.identifier.loc, description: 'Cannot use `fire` outside of a useEffect function', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts new file mode 100644 index 0000000000..2fd3244fc5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts @@ -0,0 +1,611 @@ +import {ErrorSeverity} from './CompilerErrorSeverity'; + +export enum LinterCategory { + RULES_OF_HOOKS = 'rules-of-hooks', + IMPURE_FUNCTIONS = 'impure-functions', + JSX_IN_TRY = 'jsx-in-try', + NO_REF_ACCESS_IN_RENDER = 'no-ref-access-in-render', + VALIDATE_MANUAL_MEMO = 'validate-manual-memo', + DYNAMIC_MANUAL_MEMO = 'dynamic-manual-memo', + + EXHAUSTIVE_DEPS = 'exhaustive-deps', + MEMOIZED_DEPENDENCIES = 'memoized-dependencies', // not real! + INVALID_WRITE = 'invalid-write', + CAPITALIZED_CALLS = 'capitalized-calls', + STATIC_COMPONENTS = 'static-components', + NO_SET_STATE_IN_RENDER = 'no-set-state-in-render', + NO_SET_STATE_IN_EFFECTS = 'no-set-state-in-effects', + UNNECESSARY_EFFECTS = 'unnecessary-effects', + + UNSUPPORTED_SYNTAX = 'unsupported-syntax', + + TODO_SYNTAX = 'todo-syntax', + + COMPILER_CONFIG = 'compiler-config', +} + +export enum ErrorCode { + HOOK_CALL_STATIC, + HOOK_INVALID_REFERENCE, + HOOK_CALL_REACTIVE, + HOOK_CALL_NOT_TOP_LEVEL, + IMPURE_FUNCTIONS, + JSX_IN_TRY, + NO_REF_ACCESS_IN_RENDER, + WRITE_AFTER_RENDER, + REASSIGN_IN_ASYNC, + INVALID_WRITE, + CAPITALIZED_CALLS, + STATIC_COMPONENTS, + INVALID_USE_MEMO_CALLBACK_RETURN, + INVALID_USE_MEMO_CALLBACK_PARAMETERS, + INVALID_USE_MEMO_CALLBACK_ASYNC, + INVALID_USE_MEMO_NO_ARG0, + INVALID_USE_CALLBACK_NO_ARG0, + DYNAMIC_MANUAL_MEMO_CALLBACK, + DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + + INVALID_SET_STATE_IN_RENDER, + INVALID_SET_STATE_IN_MEMO, + INVALID_SET_STATE_IN_EFFECTS, + + NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + + DYNAMIC_GATING_IS_NOT_IDENTIFIER, + DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + FILENAME_NOT_SET, + + INVALID_WRITE_GLOBAL, + INVALID_WRITE_FROZEN_VALUE_JSX, + INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + INVALID_WRITE_IMMUTABLE_ARGS, + INVALID_WRITE_STATE, + INVALID_WRITE_REDUCER_STATE, + INVALID_WRITE_EFFECT_DEPENDENCY, + INVALID_WRITE_HOOK_CAPTURED, + INVALID_WRITE_HOOK_RETURN, + INVALID_ACCESS_BEFORE_INIT, + INVALID_WRITE_GENERIC, + + MEMOIZED_EFFECT_DEPENDENCIES, + + INVALID_SYNTAX_MULTIPLE_DEFAULTS, + INVALID_SYNTAX_REASSIGNED_CONST, + INVALID_SYNTAX_BAD_VARIABLE_DECL, + UNSUPPORTED_WITH, + UNSUPPORTED_INNER_CLASS, + INVALID_IMPORT_EXPORT, + INVALID_TS_NAMESPACE, + UNSUPPORTED_NEW_EXPRESSION, + UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + INVALID_QUASI_LENGTHS, + INVALID_SYNTAX_DELETE_EXPRESSION, + UNSUPPORTED_THROW_EXPRESSION, + INVALID_JSX_NAMESPACED_NAME, + UNSUPPORTED_EVAL, + INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + INVALID_JAVASCRIPT_AST, + BAILOUT_ESLINT_SUPPRESSION, + BAILOUT_FLOW_SUPPRESSION, + MANUAL_MEMO_MUTATED_LATER, + MANUAL_MEMO_REMOVED, + MANUAL_MEMO_DEPENDENCIES_CONFLICT, + + CANNOT_COMPILE_FIRE, + DID_NOT_INFER_DEPS, + + /** Todo syntax */ + TODO_CONFLICTING_FBT_IDENTIFIER, + TODO_DUPLICATE_FBT_TAGS, + UNKNOWN_FUNCTION_PARAMETERS, +} + +type ErrorCodeType = { + code: ErrorCode; + description?: string; + severity: ErrorSeverity; + reason: string; + linterCategory: LinterCategory | null; +}; + +export const ErrorCodeDetails: Record = { + [ErrorCode.HOOK_CALL_STATIC]: { + code: ErrorCode.HOOK_CALL_STATIC, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_INVALID_REFERENCE]: { + code: ErrorCode.HOOK_INVALID_REFERENCE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_REACTIVE]: { + code: ErrorCode.HOOK_CALL_REACTIVE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_NOT_TOP_LEVEL]: { + code: ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.IMPURE_FUNCTIONS]: { + code: ErrorCode.IMPURE_FUNCTIONS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot call impure functions during render', + description: + 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).', + linterCategory: LinterCategory.IMPURE_FUNCTIONS, + }, + [ErrorCode.JSX_IN_TRY]: { + code: ErrorCode.JSX_IN_TRY, + severity: ErrorSeverity.InvalidReact, + reason: 'Avoid constructing JSX within try/catch', + description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, + linterCategory: LinterCategory.JSX_IN_TRY, + }, + [ErrorCode.NO_REF_ACCESS_IN_RENDER]: { + code: ErrorCode.NO_REF_ACCESS_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access refs during render', + description: + 'React refs are values that are not needed for rendering. Refs should only be accessed ' + + 'outside of render, such as in event handlers or effects. ' + + 'Accessing a ref value (the `current` property) during render can cause your component ' + + 'not to update as expected (https://react.dev/reference/react/useRef)', + linterCategory: LinterCategory.NO_REF_ACCESS_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_EFFECTS]: { + code: ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Calling setState synchronously within an effect can trigger cascading renders', + description: + 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + + 'In general, the body of an effect should do one or both of the following:\n' + + '* Update external systems with the latest state from React.\n' + + '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + + 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + + '(https://react.dev/learn/you-might-not-need-an-effect)', + linterCategory: LinterCategory.NO_SET_STATE_IN_EFFECTS, + }, + [ErrorCode.WRITE_AFTER_RENDER]: { + code: ErrorCode.WRITE_AFTER_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot modify local variables after render completes', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.REASSIGN_IN_ASYNC]: { + code: ErrorCode.REASSIGN_IN_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variable in async function', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE]: { + code: ErrorCode.INVALID_WRITE, + severity: ErrorSeverity.InvalidReact, + reason: 'This value cannot be modified', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.CAPITALIZED_CALLS]: { + code: ErrorCode.CAPITALIZED_CALLS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config', + linterCategory: LinterCategory.CAPITALIZED_CALLS, + }, + [ErrorCode.STATIC_COMPONENTS]: { + code: ErrorCode.STATIC_COMPONENTS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot create components during render', + linterCategory: LinterCategory.STATIC_COMPONENTS, + }, + [ErrorCode.INVALID_USE_MEMO_NO_ARG0]: { + code: ErrorCode.INVALID_USE_MEMO_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useMemo`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_CALLBACK_NO_ARG0]: { + code: ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useCallback`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useMemo', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + + [ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useCallback', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not accept parameters', + description: + 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not be async or generator functions', + description: + 'useMemo() callbacks are called once and must synchronously return a value.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks must return a value', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the first argument to be an inline function expression`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list of useMemo or useCallback to be an array literal`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY]: { + code: ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_SET_STATE_IN_RENDER]: { + code: ErrorCode.INVALID_SET_STATE_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState during render may trigger an infinite loop', + description: + 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_MEMO]: { + code: ErrorCode.INVALID_SET_STATE_IN_MEMO, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState from useMemo may trigger an infinite loop', + description: + 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS]: { + code: ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', + linterCategory: LinterCategory.UNNECESSARY_EFFECTS, + }, + + /** Invalid writes */ + [ErrorCode.INVALID_WRITE_GLOBAL]: { + code: ErrorCode.INVALID_WRITE_GLOBAL, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variables declared outside of the component/hook', + description: + 'Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX]: { + code: ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + severity: ErrorSeverity.InvalidReact, + reason: `Modifying a value returned from 'useContext()' is not allowed.`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a function whose return value should not be mutated', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_STATE]: { + code: ErrorCode.INVALID_WRITE_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_REDUCER_STATE]: { + code: ErrorCode.INVALID_WRITE_REDUCER_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY]: { + code: ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_CAPTURED]: { + code: ErrorCode.INVALID_WRITE_HOOK_CAPTURED, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_RETURN]: { + code: ErrorCode.INVALID_WRITE_HOOK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_ACCESS_BEFORE_INIT]: { + code: ErrorCode.INVALID_ACCESS_BEFORE_INIT, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access variable before it is declared', + description: `Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_GENERIC]: { + code: ErrorCode.INVALID_WRITE_GENERIC, + severity: ErrorSeverity.InvalidReact, + reason: 'This modifies a variable that React considers immutable', + linterCategory: LinterCategory.INVALID_WRITE, + }, + + /** Compiler Config */ + [ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER]: { + code: ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, + severity: ErrorSeverity.InvalidReact, + reason: 'Dynamic gating directive is not a valid JavaScript identifier', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES]: { + code: ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + severity: ErrorSeverity.InvalidReact, + reason: 'Expected a single dynamic gating directive', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.FILENAME_NOT_SET]: { + code: ErrorCode.FILENAME_NOT_SET, + severity: ErrorSeverity.InvalidConfig, + reason: `Expected a filename but found none.`, + description: + "When the 'sources' config options is specified, the React compiler will only compile files with a name", + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + + /** Effect dependencies */ + [ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES]: { + code: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, + reason: + 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.MEMOIZED_DEPENDENCIES, + }, + + /** Invalid / unsupported syntax */ + [ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS]: { + code: ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST]: { + code: ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + reason: `Expect \`const\` declaration not to be reassigned`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL]: { + code: ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + reason: `Expected variable declaration to be an identifier if no initializer was provided`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_WITH]: { + code: ErrorCode.UNSUPPORTED_WITH, + reason: `JavaScript 'with' syntax is not supported`, + description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_INNER_CLASS]: { + code: ErrorCode.UNSUPPORTED_INNER_CLASS, + reason: 'Inline `class` declarations are not supported', + description: `Move class declarations outside of components/hooks`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_IMPORT_EXPORT]: { + code: ErrorCode.INVALID_IMPORT_EXPORT, + reason: + 'JavaScript `import` and `export` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_TS_NAMESPACE]: { + code: ErrorCode.INVALID_TS_NAMESPACE, + reason: + 'TypeScript `namespace` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_NEW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_NEW_EXPRESSION, + reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + reason: `Expected sequence expression to have at least one expression`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_QUASI_LENGTHS]: { + code: ErrorCode.INVALID_QUASI_LENGTHS, + reason: `Unexpected quasi and subexpression lengths in template literal`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION]: { + code: ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + reason: `Only object properties can be deleted`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_THROW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_THROW_EXPRESSION, + reason: `Throw expressions are not supported`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JSX_NAMESPACED_NAME]: { + code: ErrorCode.INVALID_JSX_NAMESPACED_NAME, + reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EVAL]: { + code: ErrorCode.UNSUPPORTED_EVAL, + reason: `The 'eval' function is not supported`, + description: + 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME]: { + code: ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + reason: 'Expected a non-reserved identifier name', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JAVASCRIPT_AST]: { + code: ErrorCode.INVALID_JAVASCRIPT_AST, + reason: 'Encountered invalid JavaScript', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.BAILOUT_ESLINT_SUPPRESSION]: { + code: ErrorCode.BAILOUT_ESLINT_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_REMOVED]: { + code: ErrorCode.MANUAL_MEMO_REMOVED, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + + /** + * This is left vague as fire is very experimental + */ + [ErrorCode.CANNOT_COMPILE_FIRE]: { + code: ErrorCode.CANNOT_COMPILE_FIRE, + reason: 'Cannot compile `fire`', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.DID_NOT_INFER_DEPS]: { + code: ErrorCode.DID_NOT_INFER_DEPS, + reason: + 'Cannot infer dependencies of this effect. This will break your build!', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + [ErrorCode.BAILOUT_FLOW_SUPPRESSION]: { + code: ErrorCode.BAILOUT_FLOW_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + /** Syntax React Compiler may eventually support */ + [ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER]: { + code: ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, + reason: 'Support local variables named `fbt`', + description: + 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.TODO_DUPLICATE_FBT_TAGS]: { + code: ErrorCode.TODO_DUPLICATE_FBT_TAGS, + reason: 'Support duplicate fbt tags', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.UNKNOWN_FUNCTION_PARAMETERS]: { + code: ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, + severity: ErrorSeverity.Todo, + reason: 'Currently unsupported function parameter syntax', + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_MUTATED_LATER]: { + code: ErrorCode.MANUAL_MEMO_MUTATED_LATER, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: [ + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', + 'This dependency may be mutated later, which could cause the value to change unexpectedly.', + ].join(''), + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT]: { + code: ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + '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.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts new file mode 100644 index 0000000000..f399cadb9d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts @@ -0,0 +1,34 @@ +export enum ErrorSeverity { + /** + * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some + * misunderstanding on the user’s part. + */ + InvalidJS = 'InvalidJS', + /** + * JS syntax that is not supported and which we do not plan to support. Developers should + * rewrite to use supported forms. + */ + UnsupportedJS = 'UnsupportedJS', + /** + * Code that breaks the rules of React. + */ + InvalidReact = 'InvalidReact', + /** + * Incorrect configuration of the compiler. + */ + InvalidConfig = 'InvalidConfig', + /** + * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve + * memoization. + */ + CannotPreserveMemoization = 'CannotPreserveMemoization', + /** + * Unhandled syntax that we don't support yet. + */ + Todo = 'Todo', + /** + * An unexpected internal error in the compiler that indicates critical issues that can panic + * the compiler. + */ + Invariant = 'Invariant', +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts index b28228339c..d04e163960 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts @@ -6,11 +6,7 @@ */ import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, CompilerErrorDetail} from '../CompilerError'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {isHookName} from '../HIR/Environment'; import { @@ -27,6 +23,7 @@ import { } from '../HIR/visitors'; import {assertExhaustive} from '../Utils/utils'; import {Result} from '../Utils/Result'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; /** * Represents the possible kinds of value which may be stored at a given Place during @@ -111,8 +108,6 @@ export function validateHooksUsage( // Once a particular hook has a conditional call error, don't report any further issues for this hook setKind(place, Kind.Error); - const reason = - 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)'; const previousError = typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined; @@ -120,15 +115,15 @@ export function validateHooksUsage( * In some circumstances such as optional calls, we may first encounter a "hook may not be referenced as normal values" error. * If that same place is also used as a conditional call, upgrade the error to a conditonal hook error */ - if (previousError === undefined || previousError.reason !== reason) { + if ( + previousError === undefined || + previousError.reason !== + ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason + ) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason, + CompilerErrorDetail.fromCode(ErrorCode.HOOK_CALL_STATIC, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -139,13 +134,8 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + CompilerErrorDetail.fromCode(ErrorCode.HOOK_INVALID_REFERENCE, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -156,14 +146,12 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', - loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }), + CompilerErrorDetail.fromCode( + ErrorCodeDetails[ErrorCode.HOOK_CALL_REACTIVE].code, + { + loc: place.loc, + }, + ), ); } } @@ -424,7 +412,7 @@ export function validateHooksUsage( } for (const [, error] of errorsByPlace) { - errors.push(error); + errors.pushErrorDetail(error); } return errors.asResult(); } @@ -446,16 +434,10 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void { : instr.value.property; const hookKind = getHookKind(fn.env, callee.identifier); if (hookKind != null) { - errors.pushErrorDetail( - new CompilerErrorDetail({ - severity: ErrorSeverity.InvalidReact, - reason: - 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', - loc: callee.loc, - description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, - suggestions: null, - }), - ); + errors.pushErrorCode(ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, { + loc: callee.loc, + description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, + }); } break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts index 31bbf8c94d..b6c3038915 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; import {HIRFunction, IdentifierId, Place} from '../HIR'; import { eachInstructionLValue, @@ -13,13 +13,17 @@ import { eachTerminalOperand, } from '../HIR/visitors'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Ok, Result} from '../Utils/Result'; /** * Validates that local variables cannot be reassigned after render. * This prevents a category of bugs in which a closure captures a * binding from one render but does not update */ -export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { +export function validateLocalsNotReassignedAfterRender( + fn: HIRFunction, +): Result { const contextVariables = new Set(); const reassignment = getContextReassignment( fn, @@ -35,9 +39,7 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable after render completes', + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER, { description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }).withDetail({ kind: 'error', @@ -45,8 +47,9 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { message: `Cannot reassign ${variable} after render completes`, }), ); - throw errors; + return errors.asResult(); } + return Ok(undefined); } function getContextReassignment( @@ -90,9 +93,7 @@ function getContextReassignment( ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable in async function', + CompilerDiagnostic.fromCode(ErrorCode.REASSIGN_IN_ASYNC, { description: 'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead', }).withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts index b33cfb1512..90c714c557 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity} from '..'; +import {CompilerError} from '..'; import { Identifier, Instruction, @@ -22,6 +22,7 @@ import { ReactiveFunctionVisitor, visitReactiveFunction, } from '../ReactiveScopes/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -108,12 +109,8 @@ class Visitor extends ReactiveFunctionVisitor { isUnmemoized(deps.identifier, this.scopes)) ) { state.push({ - reason: - 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', - description: null, - severity: ErrorSeverity.CannotPreserveMemoization, + errorCode: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null, - suggestions: null, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts index 8989cb1ac2..72e237f660 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..'; +import {CompilerError, EnvironmentConfig} from '..'; import {HIRFunction, IdentifierId} from '../HIR'; import {DEFAULT_GLOBALS} from '../HIR/Globals'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateNoCapitalizedCalls( @@ -33,8 +34,6 @@ export function validateNoCapitalizedCalls( const errors = new CompilerError(); const capitalLoadGlobals = new Map(); const capitalizedProperties = new Map(); - const reason = - 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config'; for (const [, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { switch (value.kind) { @@ -55,11 +54,9 @@ export function validateNoCapitalizedCalls( const calleeIdentifier = value.callee.identifier.id; const calleeName = capitalLoadGlobals.get(calleeIdentifier); if (calleeName != null) { - CompilerError.throwInvalidReact({ - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${calleeName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; @@ -78,12 +75,9 @@ export function validateNoCapitalizedCalls( const propertyIdentifier = value.property.identifier.id; const propertyName = capitalizedProperties.get(propertyIdentifier); if (propertyName != null) { - errors.push({ - severity: ErrorSeverity.InvalidReact, - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${propertyName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts index d026a94ed4..5ec74f0dae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity, SourceLocation} from '..'; +import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, BlockId, @@ -19,6 +19,8 @@ import { eachInstructionValueOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Result} from '../Utils/Result'; /** * Validates that useEffect is not used for derived computations which could/should @@ -43,7 +45,9 @@ import { * const fullName = firstName + ' ' + lastName; * ``` */ -export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { +export function validateNoDerivedComputationsInEffects( + fn: HIRFunction, +): Result { const candidateDependencies: Map = new Map(); const functions: Map = new Map(); const locals: Map = new Map(); @@ -96,9 +100,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { } } } - if (errors.hasErrors()) { - throw errors; - } + return errors.asResult(); } function validateEffect( @@ -218,13 +220,6 @@ function validateEffect( } for (const loc of setStateLocations) { - errors.push({ - reason: - 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', - description: null, - severity: ErrorSeverity.InvalidReact, - loc, - suggestions: null, - }); + errors.pushErrorCode(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, {loc}); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts index 7a79c74780..f829e4b92a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts @@ -5,7 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; +import {ErrorCode} from '../CompilerError'; import { FunctionEffect, HIRFunction, @@ -65,9 +66,7 @@ export function validateNoFreezingKnownMutableFunctions( ? `\`${place.identifier.name.value}\`` : 'a local variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot modify local variables after render completes', + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER, { description: `This argument is a function which may reassign or mutate ${variable} after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts index 85adb79ceb..ee452fc08a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {HIRFunction} from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -35,19 +36,13 @@ export function validateNoImpureFunctionsInRender( ); if (signature != null && signature.impure === true) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail({ kind: 'error', loc: callee.loc, - message: 'Cannot call impure function', + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts index eea6c0a08e..bab00d7159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts @@ -5,8 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {BlockId, HIRFunction} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -35,11 +36,7 @@ export function validateNoJSXInTryStatement( case 'JsxExpression': case 'JsxFragment': { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Avoid constructing JSX within try/catch', - description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.JSX_IN_TRY).withDetail({ kind: 'error', loc: value.loc, message: 'Avoid constructing JSX within try/catch', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts index e1c17625f4..1ffc5d013c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { BlockId, HIRFunction, @@ -26,6 +22,7 @@ import { eachPatternOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Err, Ok, Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -467,11 +464,9 @@ function validateNoRefAccessInRenderImpl( if (fnType.fn.readRefEffect) { didError = true; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.NO_REF_ACCESS_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, message: `This function accesses a ref value`, @@ -730,15 +725,13 @@ function destructure( function guardCheck(errors: CompilerError, operand: Place, env: Env): void { if (env.get(operand.identifier.id)?.kind === 'Guard') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -754,15 +747,13 @@ function validateNoRefValueAccess( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -780,15 +771,13 @@ function validateNoRefPassedToFunction( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Passing a ref to a function may read its value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Passing a ref to a function may read its value during render`, + }, + ), ); } } @@ -802,15 +791,13 @@ function validateNoRefUpdate( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'Ref' || type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Cannot update ref during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Cannot update ref during render`, + }, + ), ); } } @@ -823,21 +810,13 @@ function validateNoDirectRefValueAccess( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: type.loc ?? operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: type.loc ?? operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } - -const ERROR_DESCRIPTION = - 'React refs are values that are not needed for rendering. Refs should only be accessed ' + - 'outside of render, such as in event handlers or effects. ' + - 'Accessing a ref value (the `current` property) during render can cause your component ' + - 'not to update as expected (https://react.dev/reference/react/useRef)'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts index 9c4efa380c..0a5638277d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { HIRFunction, IdentifierId, @@ -20,6 +16,7 @@ import { Place, } from '../HIR'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -95,19 +92,9 @@ export function validateNoSetStateInEffects( const setState = setStateFunctions.get(arg.identifier.id); if (setState !== undefined) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState synchronously within an effect can trigger cascading renders', - description: - 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + - 'In general, the body of an effect should do one or both of the following:\n' + - '* Update external systems with the latest state from React.\n' + - '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + - 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + - '(https://react.dev/learn/you-might-not-need-an-effect)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + ).withDetail({ kind: 'error', loc: setState.loc, message: diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts index 81209d61c6..dc67540ee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts @@ -5,14 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, isSetStateType} from '../HIR'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -127,14 +124,9 @@ function validateNoSetStateInRenderImpl( ) { if (activeManualMemoId !== null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState from useMemo may trigger an infinite loop', - description: - 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_MEMO, + ).withDetail({ kind: 'error', loc: callee.loc, message: 'Found setState() within useMemo()', @@ -142,17 +134,12 @@ function validateNoSetStateInRenderImpl( ); } else if (unconditionalBlocks.has(block.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState during render may trigger an infinite loop', - description: - 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, - message: 'Found setState() within useMemo()', + message: 'Found setState() call here', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index 6bb59247da..1e8a501996 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCode, ErrorSeverity, } from '../CompilerError'; import { @@ -280,13 +281,8 @@ function validateInferredDep( } } errorState.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - '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. ', + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, { + description: DEBUG || // If the dependency is a named variable then we can report it. Otherwise only print in debug mode (dep.identifier.name != null && dep.identifier.name.kind === 'named') @@ -300,9 +296,6 @@ function validateInferredDep( : 'Inferred dependency not present in source' }.` : '', - ] - .join('') - .trim(), suggestions: null, }).withDetail({ kind: 'error', @@ -534,15 +527,9 @@ class Visitor extends ReactiveFunctionVisitor { !this.prunedScopes.has(identifier.scope.id) ) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', - 'This dependency may be mutated later, which could cause the value to change unexpectedly.', - ].join(''), - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.MANUAL_MEMO_MUTATED_LATER, + ).withDetail({ kind: 'error', loc, message: 'This dependency may be modified later', @@ -582,18 +569,10 @@ class Visitor extends ReactiveFunctionVisitor { for (const identifier of decls) { if (isUnmemoized(identifier, this.scopes)) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. ', - DEBUG - ? `${printIdentifier(identifier)} was not memoized.` - : '', - ] - .join('') - .trim(), + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_REMOVED, { + description: DEBUG + ? `${printIdentifier(identifier)} was not memoized.` + : '', }).withDetail({ kind: 'error', loc, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts index 7f5fb408b4..12722fa2d5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, SourceLocation} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -64,9 +61,7 @@ export function validateStaticComponents( ); if (location != null) { error.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot create components during render', + CompilerDiagnostic.fromCode(ErrorCode.STATIC_COMPONENTS, { description: `Components created during render will reset their state each time they are created. Declare components outside of render. `, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts index 69ab401c89..05531ff211 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateUseMemo(fn: HIRFunction): Result { @@ -73,13 +70,9 @@ export function validateUseMemo(fn: HIRFunction): Result { ? firstParam.loc : firstParam.place.loc; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks may not accept parameters', - description: - 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + ).withDetail({ kind: 'error', loc, message: 'Callbacks with parameters are not supported', @@ -89,14 +82,9 @@ export function validateUseMemo(fn: HIRFunction): Result { if (body.loweredFunc.func.async || body.loweredFunc.func.generator) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'useMemo() callbacks may not be async or generator functions', - description: - 'useMemo() callbacks are called once and must synchronously return a value.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + ).withDetail({ kind: 'error', loc: body.loc, message: 'Async and generator functions are not supported', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md index ce42e65125..e47107723f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-component-tag-function.ts:3:4 1 | function Component() { 2 | const Foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | return ; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md index ee57ea6eb0..5acfcde730 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md @@ -22,13 +22,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-jsx-children.ts:3:4 1 | function Component() { 2 | const foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | // Children are generally access/called during render, so 6 | // modifying a global in a children function is almost diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md index 8476885de7..eb2516e386 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md @@ -18,13 +18,15 @@ function Component() { ``` Found 1 error: -Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.assign-global-in-jsx-spread-attribute.ts:4:4 2 | function Component() { 3 | const foo = () => { > 4 | someGlobal = true; - | ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + | ^^^^^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | return
; 7 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md index 6e522e1666..a5530a1180 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md @@ -20,7 +20,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `$FlowFixMe[react-rule-hook]` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `$FlowFixMe[react-rule-hook]` error.bailout-on-flow-suppression.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index 3221f97731..b348bc6191 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -23,7 +23,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable my-app/react-rule` error.bailout-on-suppression-of-custom-rule.ts:3:0 1 | // @eslintSuppressionRules:["my-app","react-rule"] @@ -36,7 +36,7 @@ error.bailout-on-suppression-of-custom-rule.ts:3:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line my-app/react-rule` error.bailout-on-suppression-of-custom-rule.ts:7:2 5 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md index 6e9887c5ac..d6e0a07d7a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md @@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md index e5c28e6e36..71f96f1ab5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md @@ -19,7 +19,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md index a8a83f6b11..ef6f6091a6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md @@ -17,7 +17,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `callback` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md index 4b49c5f653..96485fb584 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md @@ -17,12 +17,12 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `x` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-assignment-to-global.ts:2:3 1 | function useFoo(props) { > 2 | [x] = props; - | ^ `x` cannot be reassigned + | ^ `x` should not be reassigned 3 | return {x}; 4 | } 5 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md index 6da3b558bd..bae0df94af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-to-local-global-variables.ts:3:6 1 | function Component(props) { 2 | let a; > 3 | [a, b] = props.value; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 4 | 5 | return [a, b]; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md index 8e8b7917d7..f3f2cff6e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md @@ -39,13 +39,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-global-reassignment-indirect.ts:9:4 7 | 8 | const setGlobal = () => { > 9 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 10 | }; 11 | const indirectSetGlobal = () => { 12 | setGlobal(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md index 291d3873b4..8c512ae350 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md @@ -42,13 +42,13 @@ Found 1 error: Error: Cannot access variable before it is declared -`setState` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized error.invalid-hoisting-setstate.ts:19:18 17 | * $2 = Function context=setState 18 | */ > 19 | useEffect(() => setState(2), []); - | ^^^^^^^^ `setState` accessed before it is declared + | ^^^^^^^^ `setState` is accessed before it is declared 20 | 21 | const [state, setState] = useState(0); 22 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md index 3155d64329..cf6b33ce8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md index 4ac7af5bae..768e0300bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md @@ -23,7 +23,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.invalid-mutation-of-possible-props-phi-indirect.ts:4:4 2 | let x = cond ? someGlobal : props.foo; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md index 437fbcb38c..de59216e60 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md @@ -48,7 +48,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md index 25b9a5c186..72bfa49422 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md @@ -15,7 +15,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign a `const` variable +Error: Expect `const` declaration not to be reassigned `x` is declared as const. @@ -23,7 +23,7 @@ error.invalid-reassign-const.ts:3:2 1 | function Component() { 2 | const x = 0; > 3 | x = 1; - | ^ Cannot reassign a `const` variable + | ^ Expect `const` declaration not to be reassigned 4 | } 5 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md index 6379515a05..1b5942449b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md @@ -17,7 +17,7 @@ function useFoo() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md index 368b312022..8a27203097 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md @@ -49,7 +49,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md index 8c7973377d..309fd02906 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md @@ -50,7 +50,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index 3ecbcc97c3..1b4f26b2c2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -43,7 +43,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md index 96be8584be..14d6068b97 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ @@ -32,7 +32,7 @@ error.invalid-sketchy-code-use-forget.ts:1:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:5:2 3 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md index e19cee7532..d77327b90d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md @@ -40,7 +40,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-unclosed-eslint-suppression.ts:2:0 1 | // Note: Everything below this is sketchy diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md index fa9e20a418..f10764dc70 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md @@ -29,7 +29,7 @@ error.invalid-unconditional-set-state-in-render.ts:6:2 4 | const aliased = setX; 5 | > 6 | setX(1); - | ^^^^ Found setState() within useMemo() + | ^^^^ Found setState() call here 7 | aliased(2); 8 | 9 | return x; @@ -42,7 +42,7 @@ error.invalid-unconditional-set-state-in-render.ts:7:2 5 | 6 | setX(1); > 7 | aliased(2); - | ^^^^^^^ Found setState() within useMemo() + | ^^^^^^^ Found setState() call here 8 | 9 | return x; 10 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md index 337b9dd30c..db7b07261e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md @@ -34,7 +34,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `a` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md index 78530caf4a..9903cf2a1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.mutate-property-from-global.ts:4:9 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md index c7b1ac5f45..508aea8d27 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:5:4 3 | function Component(props) { @@ -34,7 +34,7 @@ error.not-useEffect-external-mutate.ts:5:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md index 89bcedf956..606c1c42c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md @@ -24,13 +24,15 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Modifying a variable defined outside a component or hook is not allowed. Consider using an effect +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.object-capture-global-mutation.ts:4:4 2 | function Foo() { 3 | const x = () => { > 4 | window.href = 'foo'; - | ^^^^^^ Modifying a variable defined outside a component or hook is not allowed. Consider using an effect + | ^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | const y = {x}; 7 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md index 2c409ea5b5..cd7f202a1c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md @@ -28,13 +28,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassign-global-fn-arg.ts:5:4 3 | export default function MyApp() { 4 | const fn = () => { > 5 | b = 2; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 6 | }; 7 | return foo(fn); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md index 8835f19ad1..6963940109 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md @@ -21,26 +21,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:4:4 2 | const foo = () => { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | }; 7 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | }; 7 | foo(); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md index 4d259dd8d4..88ae3a7ae8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md @@ -18,26 +18,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:3:2 1 | function Component() { 2 | // Cannot assign to globals > 3 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 4 | moduleLocal = true; 5 | } 6 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | // Cannot assign to globals 3 | someUnknownGlobal = true; > 4 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 5 | } 6 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md index 9c87cafff1..0bcfae2e9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md @@ -24,7 +24,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` error.sketchy-code-exhaustive-deps.ts:6:7 4 | () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md index 7077b733b0..bd1b3ed4c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md @@ -25,7 +25,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.sketchy-code-rules-of-hooks.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md index 7ffe3f84cf..544532b3b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.store-property-in-global.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md index a88d43b352..f4d192530e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md @@ -19,7 +19,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `onClick` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md index 3d54bcd75c..6f9da8fda9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-after-loop-break.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md index c892066bf8..6ab4ecb36e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md @@ -27,7 +27,7 @@ error.unconditional-set-state-in-render-after-loop.ts:6:2 4 | for (const _ of props) { 5 | } > 6 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 7 | return state; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md index a617a2f572..69c81c3ff8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-with-loop-throw.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md index dfb5c7b56f..79369f3608 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md @@ -30,7 +30,7 @@ error.unconditional-set-state-lambda.ts:8:2 6 | setX(1); 7 | }; > 8 | foo(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 9 | 10 | return [x]; 11 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md index f03b514c3f..c8a775499a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md @@ -38,7 +38,7 @@ error.unconditional-set-state-nested-function-expressions.ts:16:2 14 | bar(); 15 | }; > 16 | baz(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 17 | 18 | return [x]; 19 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md index 8432be198b..cfd27c7356 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md @@ -23,13 +23,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `renderCount` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.update-global-should-bailout.ts:3:2 1 | let renderCount = 0; 2 | function useFoo() { > 3 | renderCount += 1; - | ^^^^^^^^^^^^^^^^ `renderCount` cannot be reassigned + | ^^^^^^^^^^^^^^^^ `renderCount` should not be reassigned 4 | return renderCount; 5 | } 6 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md index 188814ee02..1531425c20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md @@ -30,9 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Expected the dependency list for useMemo to be an array literal - -Expected the dependency list for useMemo to be an array literal +Error: Expected the dependency list of useMemo or useCallback to be an array literal error.useMemo-non-literal-depslist.ts:10:4 8 | return text.toUpperCase(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md index d8250c6f57..b4f642df43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Expected a single dynamic gating directive","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md index 08eb396bb1..cf7a8cad85 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr2` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":333},"end":{"line":11,"column":43,"index":357},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":248},"end":{"line":8,"column":46,"index":292},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":277},"end":{"line":8,"column":34,"index":280},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":316},"end":{"line":11,"column":54,"index":368},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":349},"end":{"line":11,"column":42,"index":356},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index b629bfef27..0925a9b982 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -65,7 +65,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index c16890e52e..3e3135929b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -45,7 +45,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md index a9782a3b9e..a6f268555c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index babb4e8969..96b1d866fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -44,7 +44,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md index d78e4becec..bef031723c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md @@ -35,12 +35,12 @@ Found 1 error: Error: Cannot access variable before it is declared -`data` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized 9 | // TDZ violation! 10 | const onRefetch = useCallback(() => { > 11 | refetch(data); - | ^^^^ `data` accessed before it is declared + | ^^^^ `data` is accessed before it is declared 12 | }, [refetch]); 13 | 14 | // The context variable gets frozen here since it's passed to a hook diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md index 80a12e5d40..f1cc5c4808 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md @@ -22,7 +22,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | function Component(props) { @@ -35,7 +35,7 @@ error.not-useEffect-external-mutate.ts:6:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:7:4 5 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md index 41ed513912..bb4d91a4bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md @@ -22,26 +22,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | const foo = () => { 4 | // Cannot assign to globals > 5 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 6 | moduleLocal = true; 7 | }; 8 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:6:4 4 | // Cannot assign to globals 5 | someUnknownGlobal = true; > 6 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 7 | }; 8 | foo(); 9 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md index 6089255fd5..84339b0759 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md @@ -19,26 +19,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | function Component() { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | } 7 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:5:2 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | } 7 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md index 2e0c890367..8441e79925 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr2` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":365},"end":{"line":11,"column":43,"index":389},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":280},"end":{"line":8,"column":46,"index":324},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":309},"end":{"line":8,"column":34,"index":312},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":348},"end":{"line":11,"column":54,"index":400},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":381},"end":{"line":11,"column":42,"index":388},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md index 27af59e175..f433f8cb88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md @@ -24,8 +24,6 @@ Found 1 error: Error: Expected the first argument to be an inline function expression -Expected the first argument to be an inline function expression - error.validate-useMemo-named-function.ts:9:20 7 | // for now. 8 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index 006d2a49c0..bda9c3b694 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -31,7 +31,7 @@ function Component({prop1}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:4) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index 8481ed2c57..9604f69476 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -15,7 +15,7 @@ console.log(fire == null); ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index f84686bc36..02753ce345 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -32,7 +32,7 @@ function Component({props, bar}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md index 81c36a362c..cdb0726f2e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md @@ -26,7 +26,7 @@ function Component({props, bar}) { ``` Found 2 errors: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. @@ -39,7 +39,7 @@ error.invalid-outside-effect.ts:8:2 10 | useCallback(() => { 11 | fire(foo(props)); -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md index 96cea9c08f..d82f1ee1b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md @@ -27,7 +27,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md index 4dc5336ebe..a841813630 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md @@ -30,7 +30,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts new file mode 100644 index 0000000000..c3d6704504 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts @@ -0,0 +1,32 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoImpureFunctionCallsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no impure function calls rule', NoImpureFunctionCallsRule, { + valid: [], + invalid: [ + { + name: 'Known impure function calls are caught', + code: normalizeIndent` + function Component() { + const date = Date.now(); + const now = performance.now(); + const rand = Math.random(); + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts new file mode 100644 index 0000000000..9192087b31 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts @@ -0,0 +1,85 @@ +/** + * 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 {RulesOfHooksRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('rules-of-hooks', RulesOfHooksRule, { + valid: [ + { + name: 'Basic example', + code: normalizeIndent` + function Component() { + useHook(); + return
Hello world
; + } + `, + }, + { + name: 'Violation with Flow suppression', + code: ` + // Valid since error already suppressed with flow. + function useHook() { + if (cond) { + // $FlowFixMe[react-rule-hook] + useConditionalHook(); + } + } + `, + }, + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function useConditional() { + cond ?? useConditionalHook(); + props.cond && useConditionalHook(); + return
Hello world
; + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts new file mode 100644 index 0000000000..8463e860df --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts @@ -0,0 +1,31 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoAmbiguousJsxRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ambiguous JSX rule', NoAmbiguousJsxRule, { + valid: [], + invalid: [ + { + name: 'JSX in try blocks are warned against', + code: normalizeIndent` + function Component(props) { + let el; + try { + el = ; + } catch { + return null; + } + return el; + } + `, + errors: [makeTestCaseError(ErrorCode.JSX_IN_TRY)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts new file mode 100644 index 0000000000..821cab8007 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {NoCapitalizedCallsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidCapitalizedCallsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('no-capitalized-calls', NoCapitalizedCallsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + import Child from './Child'; + function Component() { + return <> + {Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Method call violation', + code: normalizeIndent` + import myModule from './MyModule'; + function Component() { + return <> + {myModule.Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + import Child1 from './Child1'; + import MyModule from './MyModule'; + function Component() { + return <> + {Child1()} + {MyModule.Child2()} + ; + }`, + errors: [ + invalidCapitalizedCallsRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts new file mode 100644 index 0000000000..a8498d303f --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts @@ -0,0 +1,27 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoRefAccessInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ref access in render rule', NoRefAccessInRenderRule, { + valid: [], + invalid: [ + { + name: 'validate against simple ref access in render', + code: normalizeIndent` + function Component(props) { + const ref = useRef(null); + const value = ref.current; + return value; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_REF_ACCESS_IN_RENDER)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts new file mode 100644 index 0000000000..43ec6ad010 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts @@ -0,0 +1,48 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoSetStateInEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in effects rule', NoSetStateInEffectsRule, { + valid: [], + invalid: [ + { + name: 'unconditional setState in useEffect', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + setState(s => s + 1); + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + { + name: 'conditional setState in render', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + if (state % 2 === 0) { + setState(state + 1); + } + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts new file mode 100644 index 0000000000..77ce895339 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {ValidateSetStateInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in render rule', ValidateSetStateInRenderRule, { + valid: [], + invalid: [ + { + name: 'setState in useMemo', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + return item; + }, [cond, item, init]); + + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'unconditional setState in render', + code: normalizeIndent` + function Component(props) { + const [x, setX] = useState(0); + const aliased = setX; + + setX(1); + aliased(2); + + return x; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts new file mode 100644 index 0000000000..77f6dd93fb --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {NoUnusedDirectivesRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule} from './shared-utils'; + +testRule('no unused directives rule', NoUnusedDirectivesRule, { + valid: [], + invalid: [ + { + name: "Unused 'use no forget' directive is reported when no errors are present on components", + code: normalizeIndent` + function Component() { + 'use no forget'; + return
Hello world
+ } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction Component() {\n \n return
Hello world
\n}\n', + }, + ], + }, + ], + }, + + { + name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", + code: normalizeIndent` + function notacomponent() { + 'use no forget'; + return 1 + 1; + } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', + }, + ], + }, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts new file mode 100644 index 0000000000..c0e0cf07c9 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts @@ -0,0 +1,172 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + testRule, + makeTestCaseError, + TestRecommendedRules, +} from './shared-utils'; +import {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; + +testRule('plugin-recommended', TestRecommendedRules, { + valid: [ + { + name: 'Basic example with component syntax', + code: normalizeIndent` + export default component HelloWorld( + text: string = 'Hello!', + onClick: () => void, + ) { + return
{text}
; + } + `, + }, + + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Multiple diagnostic kinds from the same function are surfaced', + code: normalizeIndent` + import Child from './Child'; + function Component() { + const result = cond ?? useConditionalHook(); + return <> + {Child(result)} + ; + } + `, + errors: [ + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + { + name: 'Multiple diagnostics within the same file are surfaced', + code: normalizeIndent` + function useConditional1() { + 'use memo'; + return cond ?? useConditionalHook(); + } + function useConditional2(props) { + 'use memo'; + return props.cond && useConditionalHook(); + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + { + name: "'use no forget' does not disable eslint rule", + code: normalizeIndent` + let count = 0; + function Component() { + 'use no forget'; + return cond ?? useConditionalHook(); + + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple non-fatal useMemo diagnostics are surfaced', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + }, [cond, item, init]); + + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'Pipeline errors are reported', + code: normalizeIndent` + import useMyEffect from 'useMyEffect'; + import {AUTODEPS} from 'react'; + function Component({a}) { + 'use no memo'; + useMyEffect(() => console.log(a.b), AUTODEPS); + return
Hello world
; + } + `, + options: [ + { + environment: { + inferEffectDependencies: [ + { + function: { + source: 'useMyEffect', + importSpecifierName: 'default', + }, + autodepsIndex: 1, + }, + ], + }, + }, + ], + errors: [ + { + message: /Cannot infer dependencies of this effect/, + }, + ], + }, + ], +}); + +testRule('rules that are not enabled do not error', StaticComponentsRule, { + valid: [ + { + name: 'simple case', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + }, + ], + invalid: [], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts deleted file mode 100644 index bff40e9649..0000000000 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts +++ /dev/null @@ -1,287 +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 {ErrorSeverity} from 'babel-plugin-react-compiler/src'; -import {RuleTester as ESLintTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)![0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: ESLintTester.ValidTestCase[]; - invalid: ESLintTester.InvalidTestCase[]; -}; - -const tests: CompilerTestCases = { - valid: [ - { - name: 'Basic example', - code: normalizeIndent` - function foo(x, y) { - if (x) { - return foo(false, y); - } - return [y * 10]; - } - `, - }, - { - name: 'Violation with Flow suppression', - code: ` - // Valid since error already suppressed with flow. - function useHookWithHook() { - if (cond) { - // $FlowFixMe[react-rule-hook] - useConditionalHook(); - } - } - `, - }, - { - name: 'Basic example with component syntax', - code: normalizeIndent` - export default component HelloWorld( - text: string = 'Hello!', - onClick: () => void, - ) { - return
{text}
; - } - `, - }, - { - name: 'Unsupported syntax', - code: normalizeIndent` - function foo(x) { - var y = 1; - return y * x; - } - `, - }, - { - // OK because invariants are only meant for the compiler team's consumption - name: '[Invariant] Defined after use', - code: normalizeIndent` - function Component(props) { - let y = function () { - m(x); - }; - - let x = { a }; - m(x); - return y; - } - `, - }, - { - name: "Classes don't throw", - code: normalizeIndent` - class Foo { - #bar() {} - } - `, - }, - ], - invalid: [ - { - name: 'Reportable levels can be configured', - options: [{reportableLevels: new Set([ErrorSeverity.Todo])}], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: '[InvalidReact] ESlint suppression', - // Indentation is intentionally weird so it doesn't add extra whitespace - code: normalizeIndent` - function Component(props) { - // eslint-disable-next-line react-hooks/rules-of-hooks - return
{props.foo}
; - }`, - errors: [ - { - message: /React Compiler has skipped optimizing this component/, - suggestions: [ - { - output: normalizeIndent` - function Component(props) { - - return
{props.foo}
; - }`, - }, - ], - }, - { - message: - "Definition for rule 'react-hooks/rules-of-hooks' was not found.", - }, - ], - }, - { - name: 'Multiple diagnostics are surfaced', - options: [ - { - reportableLevels: new Set([ - ErrorSeverity.Todo, - ErrorSeverity.InvalidReact, - ]), - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - } - function Bar(props) { - props.a.b = 2; - return
{props.c}
- }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - { - message: /Modifying component props or hook arguments is not allowed/, - }, - ], - }, - { - name: 'Test experimental/unstable report all bailouts mode', - options: [ - { - reportableLevels: new Set([ErrorSeverity.InvalidReact]), - __unstable_donotuse_reportAllBailouts: true, - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: "'use no forget' does not disable eslint rule", - code: normalizeIndent` - let count = 0; - function Component() { - 'use no forget'; - count = count + 1; - return
Hello world {count}
- } - `, - errors: [ - { - message: - /Cannot reassign variables declared outside of the component\/hook/, - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on components", - code: normalizeIndent` - function Component() { - 'use no forget'; - return
Hello world
- } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction Component() {\n \n return
Hello world
\n}\n', - }, - ], - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", - code: normalizeIndent` - function notacomponent() { - 'use no forget'; - return 1 + 1; - } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', - }, - ], - }, - ], - }, - { - name: 'Pipeline errors are reported', - code: normalizeIndent` - import useMyEffect from 'useMyEffect'; - import {AUTODEPS} from 'react'; - function Component({a}) { - 'use no memo'; - useMyEffect(() => console.log(a.b), AUTODEPS); - return
Hello world
; - } - `, - options: [ - { - environment: { - inferEffectDependencies: [ - { - function: { - source: 'useMyEffect', - importSpecifierName: 'default', - }, - autodepsIndex: 1, - }, - ], - }, - }, - ], - errors: [ - { - message: /Cannot infer dependencies of this effect/, - }, - ], - }, - ], -}; - -const eslintTester = new ESLintTester({ - parser: require.resolve('hermes-eslint'), - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, -}); -eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts index 5a2bea6852..87baf724e1 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts @@ -6,22 +6,11 @@ */ import {RuleTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)[0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: RuleTester.ValidTestCase[]; - invalid: RuleTester.InvalidTestCase[]; -}; +import { + CompilerTestCases, + normalizeIndent, + TestRecommendedRules, +} from './shared-utils'; const tests: CompilerTestCases = { valid: [ @@ -70,6 +59,7 @@ const tests: CompilerTestCases = { }; const eslintTester = new RuleTester({ + // @ts-ignore[2353] - outdated types parser: require.resolve('@typescript-eslint/parser'), }); -eslintTester.run('react-compiler', ReactCompilerRule, tests); +eslintTester.run('react-compiler', TestRecommendedRules, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts new file mode 100644 index 0000000000..b2f4b54994 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts @@ -0,0 +1,44 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidStaticComponentsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('static-components', StaticComponentsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function Example(props) { + const Component = new ComponentFactory(); + return ; + } + `, + errors: [invalidStaticComponentsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function Example(props) { + const Component1 = new ComponentFactory(); + const Component2 = new ComponentFactory(); + return <> + + + ; + }`, + errors: [ + invalidStaticComponentsRuleErrorMessage, + invalidStaticComponentsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts new file mode 100644 index 0000000000..8d7f481f15 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts @@ -0,0 +1,36 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {UnnecessaryEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('unnecessary effects rule', UnnecessaryEffectsRule, { + valid: [], + invalid: [ + { + name: 'test case from React docs', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + // https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state + function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + + // 🔴 Avoid: redundant state and unnecessary Effect + const [fullName, setFullName] = useState(''); + useEffect(() => { + setFullName(capitalize(firstName + ' ' + lastName)); + }, [firstName, lastName]); + + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts new file mode 100644 index 0000000000..85c937a2de --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts @@ -0,0 +1,43 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {UseMemoRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('use memo rule', UseMemoRule, { + valid: [], + invalid: [ + { + name: 'Simple async violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component({a, b}) { + let x = useMemo(async () => { + await a; + }, []); + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC)], + }, + { + name: 'Simple parameters violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component() { + let x = useMemo(c => a, []); + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts new file mode 100644 index 0000000000..f9553730e4 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts @@ -0,0 +1,98 @@ +import {RuleTester as ESLintTester, Rule} from 'eslint'; +import { + ErrorCode, + ErrorCodeDetails, +} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import escape from 'regexp.escape'; +import {configs} from '../src/index'; + +/** + * A string template tag that removes padding from the left side of multi-line strings + * @param {Array} strings array of code strings (only one expected) + */ +export function normalizeIndent(strings: TemplateStringsArray): string { + const codeLines = strings[0].split('\n'); + const leftPadding = codeLines[1].match(/\s+/)![0]; + return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); +} + +export type CompilerTestCases = { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; +}; + +export const invalidHookRuleErrorMessage: ESLintTester.TestCaseError = { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason), + ), +}; + +export const invalidCapitalizedCallsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.CAPITALIZED_CALLS].reason), + ), + }; + +export const invalidStaticComponentsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.STATIC_COMPONENTS].reason), + ), + }; + +export function makeTestCaseError(code: ErrorCode): ESLintTester.TestCaseError { + return { + message: new RegExp(escape(ErrorCodeDetails[code].reason)), + }; +} + +export function testRule( + name: string, + rule: Rule.RuleModule, + tests: { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; + }, +): void { + const eslintTester = new ESLintTester({ + // @ts-ignore[2353] - outdated types + parser: require.resolve('hermes-eslint'), + parserOptions: { + ecmaVersion: 2015, + sourceType: 'module', + enableExperimentalComponentSyntax: true, + }, + }); + + eslintTester.run(name, rule, tests); +} + +/** + * Aggregates all recommended rules from the plugin. + */ +export const TestRecommendedRules: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Disallow capitalized function calls', + category: 'Possible Errors', + recommended: true, + }, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context) { + for (const rule of Object.values( + configs.recommended.plugins['react-compiler'].rules, + )) { + const listener = rule.create(context); + if (Object.entries(listener).length !== 0) { + throw new Error('TODO: handle rules that return listeners to eslint'); + } + } + return {}; + }, +}; + +test('no test', () => {}); diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index 2dd191f033..e5402611e2 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -24,11 +24,13 @@ "@babel/preset-typescript": "^7.18.6", "@babel/types": "^7.26.0", "@types/eslint": "^8.56.12", + "@types/jest": "^30.0.0", "@types/node": "^20.2.5", "babel-jest": "^29.0.3", "eslint": "8.57.0", "hermes-eslint": "^0.25.1", - "jest": "^29.5.0" + "jest": "^29.5.0", + "regexp.escape": "^2.0.1" }, "engines": { "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" diff --git a/compiler/packages/eslint-plugin-react-compiler/src/index.ts b/compiler/packages/eslint-plugin-react-compiler/src/index.ts index a3577a101e..681e9844ef 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/index.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/index.ts @@ -5,29 +5,61 @@ * LICENSE file in the root directory of this source tree. */ -import ReactCompilerRule from './rules/ReactCompilerRule'; +import * as rules from './rules/ReactCompilerRule'; const meta = { name: 'eslint-plugin-react-compiler', }; -const rules = { - 'react-compiler': ReactCompilerRule, +/** + * Validates React components and hooks follow rules of React + * and follow best practices + */ +const validationRules = { + 'rules-of-hooks': rules.RulesOfHooksRule, + 'no-capitalized-calls': rules.NoCapitalizedCallsRule, + 'no-unstable-components': rules.StaticComponentsRule, + 'validate-use-memo-callbacks': rules.UseMemoRule, + 'validate-writes': rules.InvalidWritesRule, + 'no-unsafe-refs': rules.UnsafeRefsRule, + 'validate-set-state-in-render': rules.ValidateSetStateInRenderRule, + 'no-set-state-in-effects': rules.NoSetStateInEffectsRule, + 'no-ref-access-in-render': rules.NoRefAccessInRenderRule, + 'no-impure-function-calls': rules.NoImpureFunctionCallsRule, + 'unnecessary-effects': rules.UnnecessaryEffectsRule, + 'no-ambiguous-jsx': rules.NoAmbiguousJsxRule, +}; + +const recommendedRules = { + ...validationRules, + 'no-unsupported-syntax': rules.NoUnsupportedSyntaxRule, + + /** Validation for React Compiler inline configuration */ + 'no-unused-directives': rules.NoUnusedDirectivesRule, + 'validate-compiler-config': rules.ValidateCompilerConfigRule, +}; + +const allRules = { + ...recommendedRules, + /** Warn on syntax that React Compiler cannot transform */ + 'no-todo-syntax': rules.WarnOnTodoSyntaxRule, + 'warn-on-compilation-failures': rules.WarnOnUnactionableFailuresRule, }; const configs = { recommended: { plugins: { 'react-compiler': { - rules: { - 'react-compiler': ReactCompilerRule, - }, + rules: recommendedRules, }, }, - rules: { - 'react-compiler/react-compiler': 'error' as const, - }, + rules: Object.fromEntries( + Object.keys(recommendedRules).map(ruleName => [ + 'react-compiler/' + ruleName, + 'error', + ]), + ) as Record, }, }; -export {configs, rules, meta}; +export {configs, allRules as rules, meta}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index 730b6ff6f8..0058464090 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -5,43 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import {transformFromAstSync} from '@babel/core'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import type {SourceLocation as BabelSourceLocation} from '@babel/types'; -import BabelPluginReactCompiler, { - CompilerDiagnostic, +import { CompilerDiagnosticOptions, - CompilerErrorDetail, CompilerErrorDetailOptions, CompilerSuggestionOperation, - ErrorSeverity, - parsePluginOptions, - validateEnvironmentConfig, - OPT_OUT_DIRECTIVES, - type PluginOptions, } from 'babel-plugin-react-compiler/src'; -import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Rule} from 'eslint'; -import {Statement} from 'estree'; -import * as HermesParser from 'hermes-parser'; +import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler'; +import {LinterCategory} from 'babel-plugin-react-compiler/src/CompilerError'; function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } -const DEFAULT_REPORTABLE_LEVELS = new Set([ - ErrorSeverity.InvalidReact, - ErrorSeverity.InvalidJS, -]); -let reportableLevels = DEFAULT_REPORTABLE_LEVELS; - -function isReportableDiagnostic( - detail: CompilerErrorDetail | CompilerDiagnostic, -): boolean { - return reportableLevels.has(detail.severity); -} - function makeSuggestions( detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array { @@ -95,28 +72,97 @@ function makeSuggestions( return suggest; } -const COMPILER_OPTIONS: Partial = { - noEmit: true, - panicThreshold: 'none', - // Don't emit errors on Flow suppressions--Flow already gave a signal - flowSuppressions: false, - environment: validateEnvironmentConfig({ - validateRefAccessDuringRender: true, - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - }), -}; +function getReactCompilerResult(context: Rule.RuleContext): RunCacheEntry { + // Compat with older versions of eslint + const sourceCode = context.sourceCode ?? context.getSourceCode(); + const filename = context.filename ?? context.getFilename(); + const userOpts = context.options[0] ?? {}; -const rule: Rule.RuleModule = { + const results = runReactCompiler({ + sourceCode, + filename, + userOpts, + }); + + return results; +} + +function hasFlowSuppression( + program: RunCacheEntry, + nodeLoc: BabelSourceLocation, + suppressions: Array, +): boolean { + for (const commentNode of program.flowSuppressions) { + if ( + suppressions.includes(commentNode.code) && + commentNode.line === nodeLoc.start.line - 1 + ) { + return true; + } + } + return false; +} + +function makeRule(filter: Array): Rule.RuleModule['create'] { + return (context: Rule.RuleContext): Rule.RuleListener => { + const result = getReactCompilerResult(context); + + for (const event of result.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if ( + detail.linterCategory != null && + filter.includes(detail.linterCategory) + ) { + const loc = detail.primaryLocation(); + if (loc == null || typeof loc === 'symbol') { + continue; + } + if ( + hasFlowSuppression(result, loc, [ + 'react-rule-hook', + 'react-rule-unsafe-ref', + ]) + ) { + // If Flow already caught this error, we don't need to report it again. + continue; + } + // TODO: if multiple rules report the same linter category, + // we should deduplicate them with a "reported" set + context.report({ + message: detail.printErrorMessage(result.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); + } + } + } + return {}; + }; +} + +const unactionableBailouts: Rule.RuleModule = { meta: { type: 'problem', docs: { - description: 'Surfaces diagnostics from React Forget', + description: 'Surfaces unactionable compilation bailouts', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + }, + create(context: Rule.RuleContext): Rule.RuleListener { + return {}; + }, +}; + +export const RulesOfHooksRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to the rules of hooks', recommended: true, }, fixable: 'code', @@ -124,234 +170,284 @@ const rule: Rule.RuleModule = { // validation is done at runtime with zod schema: [{type: 'object', additionalProperties: true}], }, - create(context: Rule.RuleContext) { - // Compat with older versions of eslint - const sourceCode = context.sourceCode ?? context.getSourceCode(); - const filename = context.filename ?? context.getFilename(); - const userOpts = context.options[0] ?? {}; - if ( - userOpts.reportableLevels != null && - userOpts.reportableLevels instanceof Set - ) { - reportableLevels = userOpts.reportableLevels; - } else { - reportableLevels = DEFAULT_REPORTABLE_LEVELS; - } - /** - * Experimental setting to report all compilation bailouts on the compilation - * unit (e.g. function or hook) instead of the offensive line. - * Intended to be used when a codebase is 100% reliant on the compiler for - * memoization (i.e. deleted all manual memo) and needs compilation success - * signals for perf debugging. - */ - let __unstable_donotuse_reportAllBailouts: boolean = false; - if ( - userOpts.__unstable_donotuse_reportAllBailouts != null && - typeof userOpts.__unstable_donotuse_reportAllBailouts === 'boolean' - ) { - __unstable_donotuse_reportAllBailouts = - userOpts.__unstable_donotuse_reportAllBailouts; - } + create: makeRule([LinterCategory.RULES_OF_HOOKS]), +}; - let shouldReportUnusedOptOutDirective = true; - const options: PluginOptions = parsePluginOptions({ - ...COMPILER_OPTIONS, - ...userOpts, - environment: { - ...COMPILER_OPTIONS.environment, - ...userOpts.environment, - }, - }); - const userLogger: Logger | null = options.logger; - options.logger = { - logEvent: (eventFilename, event): void => { - userLogger?.logEvent(eventFilename, event); - if (event.kind === 'CompileError') { - shouldReportUnusedOptOutDirective = false; - const detail = event.detail; - const suggest = makeSuggestions(detail.options); - if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) { - const loc = detail.primaryLocation(); - const locStr = - loc != null && typeof loc !== 'symbol' - ? ` (@:${loc.start.line}:${loc.start.column})` - : ''; - /** - * Report bailouts with a smaller span (just the first line). - * Compiler bailout lints only serve to flag that a react function - * has not been optimized by the compiler for codebases which depend - * on compiler memo heavily for perf. These lints are also often not - * actionable. - */ - let endLoc; - if (event.fnLoc.end.line === event.fnLoc.start.line) { - endLoc = event.fnLoc.end; - } else { - endLoc = { - line: event.fnLoc.start.line, - // Babel loc line numbers are 1-indexed - column: - sourceCode.text.split(/\r?\n|\r|\n/g)[ - event.fnLoc.start.line - 1 - ]?.length ?? 0, - }; - } - const firstLineLoc = { - start: event.fnLoc.start, - end: endLoc, - }; - context.report({ - message: `${detail.printErrorMessage(sourceCode.text, {eslint: true})} ${locStr}`, - loc: firstLineLoc, - suggest, - }); - } +export const NoCapitalizedCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to capitalized calls', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.CAPITALIZED_CALLS]), +}; +export const StaticComponentsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'todo', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.STATIC_COMPONENTS]), +}; + +export const InvalidWritesRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.INVALID_WRITE]), +}; +export const UseMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Surfaces compilation errors related to invalid useMemo usage', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.VALIDATE_MANUAL_MEMO]), +}; + +// TODO: test cases +export const NoDynamicManualMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.DYNAMIC_MANUAL_MEMO]), +}; + +export const UnsafeRefsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to unsafe refs', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.EXHAUSTIVE_DEPS]), +}; + +export const ValidateSetStateInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_RENDER]), +}; + +export const NoSetStateInEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to setState in render', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_EFFECTS]), +}; + +export const NoRefAccessInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_REF_ACCESS_IN_RENDER]), +}; + +export const NoImpureFunctionCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.IMPURE_FUNCTIONS]), +}; + +export const UnnecessaryEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNNECESSARY_EFFECTS]), +}; + +export const NoAmbiguousJsxRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on JSX usage that is ambiguous.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.JSX_IN_TRY]), +}; + +// TODO: test cases +export const NoUnsupportedSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler does not and will not support.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNSUPPORTED_SYNTAX]), +}; + +// TODO: test cases +export const WarnOnTodoSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler currently does not support, but may in the future.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.TODO_SYNTAX]), +}; + +// TODO: test cases +export const ValidateCompilerConfigRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Validates the React Compiler configuration', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.COMPILER_CONFIG]), +}; + +export const WarnOnUnactionableFailuresRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on compilation failures that are not actionable', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const event of results.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if (detail.linterCategory == null) { const loc = detail.primaryLocation(); - if ( - !isReportableDiagnostic(detail) || - loc == null || - typeof loc === 'symbol' - ) { - return; + if (loc == null || typeof loc === 'symbol') { + continue; } - if ( - hasFlowSuppression(loc, 'react-rule-hook') || - hasFlowSuppression(loc, 'react-rule-unsafe-ref') - ) { - // If Flow already caught this error, we don't need to report it again. - return; - } - if (loc != null) { - context.report({ - message: detail.printErrorMessage(sourceCode.text, { - eslint: true, - }), - loc, - suggest, - }); - } - } - }, - }; - - try { - options.environment = validateEnvironmentConfig( - options.environment ?? {}, - ); - } catch (err: unknown) { - options.logger?.logEvent('', err as LoggerEvent); - } - - function hasFlowSuppression( - nodeLoc: BabelSourceLocation, - suppression: string, - ): boolean { - const comments = sourceCode.getAllComments(); - const flowSuppressionRegex = new RegExp( - '\\$FlowFixMe\\[' + suppression + '\\]', - ); - for (const commentNode of comments) { - if ( - flowSuppressionRegex.test(commentNode.value) && - commentNode.loc!.end.line === nodeLoc.start.line - 1 - ) { - return true; + context.report({ + message: detail.printErrorMessage(results.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); } } - return false; - } - - let babelAST; - if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { - try { - const {parse: babelParse} = require('@babel/parser'); - babelAST = babelParse(sourceCode.text, { - filename, - sourceType: 'unambiguous', - plugins: ['typescript', 'jsx'], - }); - } catch { - /* empty */ - } - } else { - try { - babelAST = HermesParser.parse(sourceCode.text, { - babel: true, - enableExperimentalComponentSyntax: true, - sourceFilename: filename, - sourceType: 'module', - }); - } catch { - /* empty */ - } - } - - if (babelAST != null) { - try { - transformFromAstSync(babelAST, sourceCode.text, { - filename, - highlightCode: false, - retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], - sourceType: 'module', - configFile: false, - babelrc: false, - }); - } catch (err) { - /* errors handled by injected logger */ - } - } - - function reportUnusedOptOutDirective(stmt: Statement) { - if ( - stmt.type === 'ExpressionStatement' && - stmt.expression.type === 'Literal' && - typeof stmt.expression.value === 'string' && - OPT_OUT_DIRECTIVES.has(stmt.expression.value) && - stmt.loc != null - ) { - context.report({ - message: `Unused '${stmt.expression.value}' directive`, - loc: stmt.loc, - suggest: [ - { - desc: 'Remove the directive', - fix(fixer) { - return fixer.remove(stmt); - }, - }, - ], - }); - } - } - if (shouldReportUnusedOptOutDirective) { - return { - FunctionDeclaration(fnDecl) { - for (const stmt of fnDecl.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - ArrowFunctionExpression(fnExpr) { - if (fnExpr.body.type === 'BlockStatement') { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - } - }, - FunctionExpression(fnExpr) { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - }; - } else { - return {}; } + return {}; }, }; -export default rule; +export const NoUnusedDirectivesRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const directive of results.unusedOptOutDirectives) { + context.report({ + message: `Unused '${directive.directive}' directive`, + loc: directive.loc, + suggest: [ + { + desc: 'Remove the directive', + fix(fixer) { + return fixer.removeRange(directive.range); + }, + }, + ], + }); + } + return {}; + }, +}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts new file mode 100644 index 0000000000..655eaf5197 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -0,0 +1,323 @@ +/** + * 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 {transformFromAstSync, traverse} from '@babel/core'; +import {parse as babelParse} from '@babel/parser'; +import {Directive, File} from '@babel/types'; +// @ts-expect-error: no types available +import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; +import BabelPluginReactCompiler, { + parsePluginOptions, + validateEnvironmentConfig, + OPT_OUT_DIRECTIVES, + type PluginOptions, +} from 'babel-plugin-react-compiler/src'; +import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; +import type {SourceCode} from 'eslint'; +import {SourceLocation} from 'estree'; +// @ts-expect-error: no types available +import * as HermesParser from 'hermes-parser'; +import {isDeepStrictEqual} from 'util'; +import type {ParseResult} from '@babel/parser'; + +const COMPILER_OPTIONS: Partial = { + noEmit: true, + panicThreshold: 'none', + // Don't emit errors on Flow suppressions--Flow already gave a signal + flowSuppressions: false, + environment: validateEnvironmentConfig({ + validateRefAccessDuringRender: true, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + // TODO: remove, this should be in the type system + validateNoCapitalizedCalls: [], + validateHooksUsage: true, + validateNoDerivedComputationsInEffects: true, + }), +}; + +export type UnusedOptOutDirective = { + loc: SourceLocation; + range: [number, number]; + directive: string; +}; +export type RunCacheEntry = { + sourceCode: string; + filename: string; + userOpts: PluginOptions; + flowSuppressions: Array<{line: number; code: string}>; + unusedOptOutDirectives: Array; + events: LoggerEvent[]; +}; + +type RunParams = { + sourceCode: SourceCode; + filename: string; + userOpts: PluginOptions; +}; +const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g; + +function getFlowSuppressions( + sourceCode: SourceCode, +): Array<{line: number; code: string}> { + const comments = sourceCode.getAllComments(); + const results: Array<{line: number; code: string}> = []; + + for (const commentNode of comments) { + const matches = commentNode.value.matchAll(FLOW_SUPPRESSION_REGEX); + for (const match of matches) { + if (match.index != null && commentNode.loc != null) { + const code = match[1]; + results.push({ + line: commentNode.loc!.end.line, + code, + }); + } + } + } + return results; +} + +function filterUnusedOptOutDirectives( + directives: ReadonlyArray, +): Array { + const results: Array = []; + for (const directive of directives) { + if ( + OPT_OUT_DIRECTIVES.has(directive.value.value) && + directive.loc != null + ) { + results.push({ + loc: directive.loc, + directive: directive.value.value, + range: [directive.start!, directive.end!], + }); + } + } + return results; +} + +function runReactCompilerImpl({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + // Compat with older versions of eslint + for (const [key, entry] of Object.entries(userOpts)) { + if (key === 'environment' && COMPILER_OPTIONS.environment != null) { + for (const envKey of Object.keys(entry as Record)) { + if ( + COMPILER_OPTIONS.environment.hasOwnProperty(envKey) && + isDeepStrictEqual( + (entry as Record)[envKey], + (COMPILER_OPTIONS.environment as Record)[envKey], + ) + ) { + console.warn('Conflicting environment option detected: ' + envKey); + } + } + } else if (COMPILER_OPTIONS.hasOwnProperty(key)) { + if (isDeepStrictEqual(entry, (COMPILER_OPTIONS as any)[key])) { + console.warn('Conflicting option detected: ' + key); + } + } + } + const options: PluginOptions = parsePluginOptions({ + ...COMPILER_OPTIONS, + ...userOpts, + environment: { + ...COMPILER_OPTIONS.environment, + ...userOpts.environment, + }, + }); + const results: RunCacheEntry = { + sourceCode: sourceCode.text, + filename, + userOpts, + flowSuppressions: [], + unusedOptOutDirectives: [], + events: [], + }; + const userLogger: Logger | null = options.logger; + options.logger = { + logEvent: (eventFilename, event): void => { + userLogger?.logEvent(eventFilename, event); + results.events.push(event); + }, + }; + + try { + options.environment = validateEnvironmentConfig(options.environment ?? {}); + } catch (err: unknown) { + options.logger?.logEvent(filename, err as LoggerEvent); + } + + let babelAST: ParseResult | null = null; + if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { + try { + babelAST = babelParse(sourceCode.text, { + sourceFilename: filename, + sourceType: 'unambiguous', + plugins: ['typescript', 'jsx'], + }); + } catch { + /* empty */ + } + } else { + try { + babelAST = HermesParser.parse(sourceCode.text, { + babel: true, + enableExperimentalComponentSyntax: true, + sourceFilename: filename, + sourceType: 'module', + }); + } catch { + /* empty */ + } + } + + if (babelAST != null) { + results.flowSuppressions = getFlowSuppressions(sourceCode); + try { + transformFromAstSync(babelAST, sourceCode.text, { + filename, + highlightCode: false, + retainLines: true, + plugins: [ + [PluginProposalPrivateMethods, {loose: true}], + [BabelPluginReactCompiler, options], + ], + sourceType: 'module', + configFile: false, + babelrc: false, + }); + + if (results.events.filter(e => e.kind === 'CompileError').length === 0) { + traverse(babelAST, { + FunctionDeclaration(path) { + path.node; + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + ArrowFunctionExpression(path) { + if (path.node.body.type === 'BlockStatement') { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + } + }, + FunctionExpression(path) { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + }); + } + } catch (err) { + /* errors handled by injected logger */ + } + } + + return results; +} + +const SENTINEL = Symbol(); + +type T = {[k: string]: any}; + +// Array backed LRU cache -- should be small < 10 elements +class LRUCache { + // newest at headIdx, then headIdx + 1, ..., tailIdx + #values: Array<[K, T | Error] | [typeof SENTINEL, void]>; + #headIdx: number = 0; + // #store: (key: K) => T | Error; + // #isValue: null | ((key: T | Error) => key is T); + + constructor( + size: number, + // store: (key: K) => T | Error, + // isValue?: (key: T | Error) => key is T, + ) { + this.#values = new Array(size).fill(SENTINEL); + // this.#store = store; + // this.#isValue = isValue ?? null; + } + + // gets a value and sets it as "recently used" + get(key: K): T | null { + let idx = this.#values.findIndex(entry => entry[0] === key); + // If found, move to front + if (idx === this.#headIdx) { + return this.#values[this.#headIdx][1] as T; + } else if (idx < 0) { + return null; + // const value = this.#store(key); + // if (this.#isValue && !this.#isValue(value)) { + // return value; // Return error directly + // } + // this.#headIdx = + // (this.#headIdx - 1 + this.#values.length) % this.#values.length; + // this.#values[this.#headIdx] = [key, value]; + } + + const entry: [K, T] = this.#values[idx] as [K, T]; + + const len = this.#values.length; + for (let i = 0; i < Math.min(idx, len - 1); i++) { + this.#values[(this.#headIdx + i + 1) % len] = + this.#values[(this.#headIdx + i) % len]; + } + this.#values[this.#headIdx] = entry; + return entry[1]; + } + push(key: K, value: T): void { + this.#headIdx = + (this.#headIdx - 1 + this.#values.length) % this.#values.length; + this.#values[this.#headIdx] = [key, value]; + } +} +const cache = new LRUCache(10); + +export default function runReactCompiler({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + const entry = cache.get(filename); + if ( + entry != null && + entry.sourceCode === sourceCode.text && + isDeepStrictEqual(entry.userOpts, userOpts) + ) { + return entry; + } else if (entry != null) { + if (process.env['DEBUG']) { + console.log( + `Cache hit for ${filename}, but source code or options changed, recomputing`, + ); + } + } + + const runEntry = runReactCompilerImpl({ + sourceCode, + filename, + userOpts, + }); + // If we have a cache entry, we can update it + if (entry != null) { + Object.assign(entry, runEntry); + } else { + cache.push(filename, runEntry); + } + return {...runEntry}; +} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index a159359773..a6041bd5cc 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -338,7 +338,16 @@ export async function transformFixtureInput( if (logs.length !== 0) { formattedLogs = logs .map(({event}) => { - return JSON.stringify(event); + return JSON.stringify(event, (key, value) => { + if ( + key === 'detail' && + value != null && + typeof value.serialize === 'function' + ) { + return value.serialize(); + } + return value; + }); }) .join('\n'); } diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 6e1bc7feeb..696261cbf5 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -542,11 +542,6 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - "@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" @@ -1605,7 +1600,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": +"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.27.0", "@babel/types@^7.27.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== @@ -1613,14 +1608,6 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -2148,6 +2135,11 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/diff-sequences@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + "@jest/environment@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz" @@ -2178,6 +2170,13 @@ "@types/node" "*" jest-mock "^29.5.0" +"@jest/expect-utils@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz#9d42e4b8bc80367db30abc6c42b2cb14073f66fc" + integrity sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew== + dependencies: + "@jest/get-type" "30.0.1" + "@jest/expect-utils@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz" @@ -2274,6 +2273,11 @@ jest-mock "^29.5.0" jest-util "^29.5.0" +"@jest/get-type@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.0.1.tgz#0d32f1bbfba511948ad247ab01b9007724fc9f52" + integrity sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw== + "@jest/globals@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz" @@ -2313,6 +2317,14 @@ "@jest/types" "^29.6.3" jest-mock "^29.7.0" +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + "@jest/reporters@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz" @@ -2435,6 +2447,13 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/schemas@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" @@ -2663,6 +2682,19 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz" @@ -2965,6 +2997,11 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sinclair/typebox@^0.34.0": + version "0.34.38" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.38.tgz#2365df7c23406a4d79413a766567bfbca708b49d" + integrity sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" @@ -3154,6 +3191,11 @@ resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== +"@types/istanbul-lib-coverage@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + "@types/istanbul-lib-report@*": version "3.0.0" resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" @@ -3176,6 +3218,13 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jest@^28.1.6": version "28.1.8" resolved "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz" @@ -3200,6 +3249,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + "@types/jsdom@^20.0.0": version "20.0.0" resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz" @@ -3282,6 +3339,11 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz" @@ -3309,6 +3371,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.33": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz" @@ -3740,7 +3809,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: +ansi-styles@^5.0.0, ansi-styles@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== @@ -3803,11 +3872,32 @@ aria-query@^5.0.0: resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.0.2.tgz" integrity sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q== +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + ast-types@^0.13.4: version "0.13.4" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz" @@ -3815,6 +3905,11 @@ ast-types@^0.13.4: dependencies: tslib "^2.0.1" +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + async@^3.2.3: version "3.2.6" resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" @@ -3825,6 +3920,13 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + axios@^1.6.1: version "1.7.4" resolved "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz" @@ -4245,7 +4347,7 @@ cac@^6.7.14: resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== @@ -4253,7 +4355,17 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -call-bound@^1.0.2: +call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== @@ -4295,7 +4407,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: +chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4377,6 +4489,11 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz" integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== +ci-info@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" @@ -4722,6 +4839,33 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + date-fns@^2.29.1: version "2.30.0" resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz" @@ -4788,6 +4932,24 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + degenerator@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz" @@ -4922,7 +5084,7 @@ dreamopt@~0.6.0: dependencies: wordwrap ">=0.0.2" -dunder-proto@^1.0.1: +dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== @@ -5033,7 +5195,67 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.1: +es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.9: + version "1.24.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" + integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== @@ -5050,6 +5272,25 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" + es5-ext@0.8.x: version "0.8.2" resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz" @@ -5428,6 +5669,18 @@ expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expect@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.0.5.tgz#c23bf193c5e422a742bfd2990ad990811de41a5a" + integrity sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ== + dependencies: + "@jest/expect-utils" "30.0.5" + "@jest/get-type" "30.0.1" + jest-matcher-utils "30.0.5" + jest-message-util "30.0.5" + jest-mock "30.0.5" + jest-util "30.0.5" + express-rate-limit@^7.5.0: version "7.5.0" resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz" @@ -5719,6 +5972,13 @@ follow-redirects@^1.15.6: resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz" @@ -5788,6 +6048,23 @@ function-bind@^1.1.2: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -5798,7 +6075,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -5819,7 +6096,7 @@ get-package-type@^0.1.0: resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proto@^1.0.1: +get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -5839,6 +6116,15 @@ get-stream@^6.0.0: resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + get-uri@^6.0.1: version "6.0.4" resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz" @@ -5957,6 +6243,14 @@ globals@^14.0.0: resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" @@ -5969,12 +6263,12 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.2.0: +gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -5989,6 +6283,11 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" @@ -5999,11 +6298,32 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.1.0: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" @@ -6231,6 +6551,15 @@ ini@^1.3.4: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" @@ -6251,6 +6580,15 @@ ipaddr.js@1.9.1: resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -6261,6 +6599,24 @@ is-arrayish@^0.3.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" @@ -6268,6 +6624,19 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-core-module@^2.9.0: version "2.10.0" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" @@ -6275,11 +6644,35 @@ is-core-module@^2.9.0: dependencies: has "^1.0.3" +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" @@ -6290,6 +6683,16 @@ is-generator-fn@^2.0.0: resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== + dependencies: + call-bound "^1.0.3" + get-proto "^1.0.0" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -6307,6 +6710,24 @@ is-interactive@^2.0.0: resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" @@ -6339,11 +6760,57 @@ is-promise@^4.0.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz" integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" @@ -6354,11 +6821,36 @@ is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0: resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2, is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-windows@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -6797,6 +7289,16 @@ jest-config@^29.7.0: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-diff@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.5.tgz#b40f81e0c0d13e5b81c4d62b0d0dfa6a524ee0fd" + integrity sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + pretty-format "30.0.5" + jest-diff@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz" @@ -7106,6 +7608,16 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-matcher-utils@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz#dff3334be58faea4a5e1becc228656fbbfc2467d" + integrity sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ== + dependencies: + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + jest-diff "30.0.5" + pretty-format "30.0.5" + jest-matcher-utils@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz" @@ -7146,6 +7658,21 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-message-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.5.tgz#dd12ffec91dd3fa6a59cbd538a513d8e239e070c" + integrity sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-message-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz" @@ -7206,6 +7733,15 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + jest-util "30.0.5" + jest-mock@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz" @@ -7237,6 +7773,11 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + jest-regex-util@^28.0.2: version "28.0.2" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" @@ -7683,6 +8224,18 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + jest-util@^28.0.0, jest-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" @@ -8327,7 +8880,7 @@ merge@^2.1.1: resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -8620,11 +9173,28 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.3: +object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + on-finished@^2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" @@ -8695,6 +9265,15 @@ ora@^7.0.1: string-width "^6.1.0" strip-ansi "^7.1.0" +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -8949,6 +9528,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + postcss-load-config@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz" @@ -8975,6 +9559,15 @@ prettier@^3.3.3: resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +pretty-format@30.0.5, pretty-format@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + pretty-format@^24: version "24.9.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz" @@ -9202,7 +9795,7 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -9251,6 +9844,20 @@ readline@^1.3.0: resolved "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz" integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz" @@ -9280,6 +9887,30 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" +regexp.escape@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexp.escape/-/regexp.escape-2.0.1.tgz#09e4beef9d202dbd739868f3818223f977cf91da" + integrity sha512-JItRb4rmyTzmERBkAf6J87LjDPy/RscIwmaJQ3gsFlAzrmZbZU8LwBw5IydFZXW9hqpgbPlGbMhtpqtuAhMgtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + for-each "^0.3.3" + safe-regex-test "^1.0.3" + +regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + regexpu-core@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz" @@ -9457,6 +10088,17 @@ rxjs@^7.0.0, rxjs@^7.8.1: dependencies: tslib "^2.1.0" +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" + isarray "^2.0.5" + safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" @@ -9467,6 +10109,23 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + safe-stable-stringify@^2.3.1: version "2.5.0" resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" @@ -9576,6 +10235,37 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" @@ -9759,6 +10449,13 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" @@ -9771,6 +10468,14 @@ stdin-discarder@^0.1.0: dependencies: bl "^5.0.0" +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + streamx@^2.15.0, streamx@^2.21.0: version "2.22.0" resolved "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz" @@ -9825,6 +10530,38 @@ string-width@^6.1.0: emoji-regex "^10.2.1" strip-ansi "^7.0.1" +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + +string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -10232,6 +10969,51 @@ type-is@^2.0.0, type-is@^2.0.1: media-typer "^1.1.0" mime-types "^3.0.0" +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + typed-query-selector@^2.12.0: version "2.12.0" resolved "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz" @@ -10251,6 +11033,16 @@ typescript@^5.4.3: resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" @@ -10460,11 +11252,64 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which@^1.2.10, which@^1.2.14: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" From 59f0a6a18dc572196eae7ec9b445cdc64fdc4f32 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 8 Aug 2025 12:18:50 -0400 Subject: [PATCH 418/422] [compiler] Aggregate error reporting in a registry All error messages that are not invariants, todos, or config validation errors are now moved to a registry. This helps us check that error messages and linked documentation is uniform and up to date. This also lets us link error codes to linter reporting categories and break the single `react-compiler` rule up into many smaller rules. Changes to error reporting: - instead of writing raw error message into your code, add a new ErrorCode to `CompilerErrorCodes` - create errors by constructing a `CompilerErrorDetailOptions` with the correct error code, or calling `CompilerErrorDetail.fromCode()` / `CompilerDiagnostic.fromCode()`. Changes to linter: - `react-compiler` lint rule is now broken up into many smaller lint rules, each with their own test cases. - linting no longer fails early when a non-fatal error is find. Instead, we now log and move on to lint for more errors! Todos: - Codebases previously using the eslint plugin may already have `react-compiler` eslint suppressions. Since this PR removes this rule, these codebases may get new eslint warnings on already-suppressed ranges. Some options I can think of: - Force everyone to add the new suppressions - Release a script to rewrite previous suppressions to the new suppressions. Codebases that had incomplete suppressions / waited to update may benefit here, as this still lets us error on previously-unseen suppressions - Hard code logic in our eslint plugin to avoid reporting if we see a `eslint-disable-next-line react-compiler`. This doesn't feel ideal - Align on eslint plugin rule names and error code mappings - I aggregated a few error codes here. For example, reassigning and modifying local variables after render now have the same error code + message. See changes fixtures for more examples --- .../src/CompilerError.ts | 267 ++++-- .../src/Entrypoint/Imports.ts | 4 +- .../src/Entrypoint/Options.ts | 9 +- .../src/Entrypoint/Pipeline.ts | 36 +- .../src/Entrypoint/Program.ts | 47 +- .../src/Entrypoint/Suppression.ts | 14 +- .../ValidateNoUntransformedReferences.ts | 33 +- .../src/Flood/TypeErrors.ts | 41 +- .../src/HIR/BuildHIR.ts | 194 ++-- .../src/HIR/Environment.ts | 10 +- .../src/HIR/HIR.ts | 15 +- .../src/HIR/HIRBuilder.ts | 27 +- .../src/Inference/DropManualMemoization.ts | 77 +- .../src/Inference/InferFunctionEffects.ts | 36 +- .../Inference/InferMutationAliasingEffects.ts | 66 +- .../src/Inference/InferReferenceEffects.ts | 16 +- .../src/Transform/TransformFire.ts | 27 +- .../src/Utils/CompilerErrorCodes.ts | 611 ++++++++++++ .../src/Utils/CompilerErrorSeverity.ts | 34 + .../src/Validation/ValidateHooksUsage.ts | 58 +- .../ValidateLocalsNotReassignedAfterRender.ts | 19 +- .../ValidateMemoizedEffectDependencies.ts | 9 +- .../Validation/ValidateNoCapitalizedCalls.ts | 14 +- .../ValidateNoDerivedComputationsInEffects.ts | 21 +- ...ValidateNoFreezingKnownMutableFunctions.ts | 7 +- .../ValidateNoImpureFunctionsInRender.ts | 19 +- .../Validation/ValidateNoJSXInTryStatement.ts | 9 +- .../Validation/ValidateNoRefAccessInRender.ts | 101 +- .../Validation/ValidateNoSetStateInEffects.ts | 23 +- .../Validation/ValidateNoSetStateInRender.ts | 31 +- .../ValidatePreservedManualMemoization.ts | 46 +- .../Validation/ValidateStaticComponents.ts | 11 +- .../src/Validation/ValidateUseMemo.ts | 28 +- ...global-in-component-tag-function.expect.md | 4 +- ...or.assign-global-in-jsx-children.expect.md | 4 +- ...n-global-in-jsx-spread-attribute.expect.md | 6 +- ...rror.bailout-on-flow-suppression.expect.md | 2 +- ...ut-on-suppression-of-custom-rule.expect.md | 4 +- ...ext-variable-only-chained-assign.expect.md | 2 +- ...variable-in-function-declaration.expect.md | 2 +- ...erences-variable-its-assigned-to.expect.md | 2 +- ...destructure-assignment-to-global.expect.md | 4 +- ...ucture-to-local-global-variables.expect.md | 4 +- ...lid-global-reassignment-indirect.expect.md | 4 +- .../error.invalid-hoisting-setstate.expect.md | 4 +- ...valid-impure-functions-in-render.expect.md | 18 +- ...n-of-possible-props-phi-indirect.expect.md | 2 +- ...eassign-local-variable-in-effect.expect.md | 2 +- .../error.invalid-reassign-const.expect.md | 4 +- ...ssign-local-in-hook-return-value.expect.md | 2 +- ...eassign-local-variable-in-effect.expect.md | 2 +- ...-local-variable-in-hook-argument.expect.md | 2 +- ...n-local-variable-in-jsx-callback.expect.md | 2 +- ....invalid-sketchy-code-use-forget.expect.md | 4 +- ...alid-unclosed-eslint-suppression.expect.md | 2 +- ...nconditional-set-state-in-render.expect.md | 4 +- ...ange-shared-inner-outer-function.expect.md | 2 +- ...rror.mutate-property-from-global.expect.md | 2 +- ...or.not-useEffect-external-mutate.expect.md | 4 +- ...r.object-capture-global-mutation.expect.md | 6 +- .../error.reassign-global-fn-arg.expect.md | 4 +- ....reassignment-to-global-indirect.expect.md | 8 +- .../error.reassignment-to-global.expect.md | 8 +- ...ror.sketchy-code-exhaustive-deps.expect.md | 2 +- ...rror.sketchy-code-rules-of-hooks.expect.md | 2 +- .../error.store-property-in-global.expect.md | 2 +- ...ences-later-variable-declaration.expect.md | 2 +- ...state-in-render-after-loop-break.expect.md | 2 +- ...l-set-state-in-render-after-loop.expect.md | 2 +- ...-state-in-render-with-loop-throw.expect.md | 2 +- ...r.unconditional-set-state-lambda.expect.md | 2 +- ...tate-nested-function-expressions.expect.md | 2 +- ...ror.update-global-should-bailout.expect.md | 4 +- ...ror.useMemo-non-literal-depslist.expect.md | 4 +- .../dynamic-gating-invalid-multiple.expect.md | 2 +- .../no-emit/retry-no-emit.expect.md | 5 +- ...setState-in-useEffect-transitive.expect.md | 2 +- .../invalid-setState-in-useEffect.expect.md | 2 +- ...valid-impure-functions-in-render.expect.md | 18 +- ...n-local-variable-in-jsx-callback.expect.md | 2 +- ...rozen-hoisted-storecontext-const.expect.md | 4 +- ...or.not-useEffect-external-mutate.expect.md | 4 +- ....reassignment-to-global-indirect.expect.md | 8 +- .../error.reassignment-to-global.expect.md | 8 +- .../new-mutability/retry-no-emit.expect.md | 5 +- ....validate-useMemo-named-function.expect.md | 2 - .../bailout-retry/error.todo-syntax.expect.md | 2 +- ...ror.untransformed-fire-reference.expect.md | 2 +- .../bailout-retry/error.use-no-memo.expect.md | 2 +- .../error.invalid-outside-effect.expect.md | 4 +- ...id-rewrite-deps-no-array-literal.expect.md | 2 +- ...rror.invalid-rewrite-deps-spread.expect.md | 2 +- .../__tests__/ImpureFunctionCallsRule-test.ts | 32 + .../__tests__/InvalidHooksRule-test.ts | 85 ++ .../__tests__/NoAmbiguousJsxRule-test.ts | 31 + .../__tests__/NoCapitalizedCallsRule-test.ts | 58 ++ .../__tests__/NoRefAccessInRender-tests.ts | 27 + .../__tests__/NoSetStateInEffects-tests.ts | 48 + .../__tests__/NoSetStateInRender-test.ts | 58 ++ .../__tests__/NoUnusedDirectivesRule-test.ts | 58 ++ .../__tests__/PluginTest-test.ts | 172 ++++ .../__tests__/ReactCompilerRule-test.ts | 287 ------ .../ReactCompilerRuleTypescript-test.ts | 24 +- .../__tests__/StaticComponentsRule-test.ts | 44 + .../__tests__/UnnecessaryEffects-tests.ts | 36 + .../__tests__/ValidateUseMemo-test.ts | 43 + .../__tests__/shared-utils.ts | 98 ++ .../eslint-plugin-react-compiler/package.json | 4 +- .../eslint-plugin-react-compiler/src/index.ts | 52 +- .../src/rules/ReactCompilerRule.ts | 626 ++++++------ .../src/shared/RunReactCompiler.ts | 323 +++++++ compiler/packages/snap/src/compiler.ts | 11 +- compiler/yarn.lock | 901 +++++++++++++++++- 113 files changed, 3763 insertions(+), 1437 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts delete mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index ee0d0f74c5..6bce5dca2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -10,48 +10,22 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {SourceLocation} from './HIR'; import {Err, Ok, Result} from './Utils/Result'; import {assertExhaustive} from './Utils/utils'; - -export enum ErrorSeverity { - /** - * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some - * misunderstanding on the user’s part. - */ - InvalidJS = 'InvalidJS', - /** - * JS syntax that is not supported and which we do not plan to support. Developers should - * rewrite to use supported forms. - */ - UnsupportedJS = 'UnsupportedJS', - /** - * Code that breaks the rules of React. - */ - InvalidReact = 'InvalidReact', - /** - * Incorrect configuration of the compiler. - */ - InvalidConfig = 'InvalidConfig', - /** - * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve - * memoization. - */ - CannotPreserveMemoization = 'CannotPreserveMemoization', - /** - * Unhandled syntax that we don't support yet. - */ - Todo = 'Todo', - /** - * An unexpected internal error in the compiler that indicates critical issues that can panic - * the compiler. - */ - Invariant = 'Invariant', -} +import {ErrorSeverity} from './Utils/CompilerErrorSeverity'; +import { + ErrorCode, + ErrorCodeDetails, + LinterCategory, +} from './Utils/CompilerErrorCodes'; +export {ErrorSeverity}; +export {ErrorCode, ErrorCodeDetails, LinterCategory}; export type CompilerDiagnosticOptions = { severity: ErrorSeverity; category: string; - description: string; + description?: string | null | undefined; details: Array; suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; }; export type CompilerDiagnosticDetail = @@ -86,13 +60,28 @@ export type CompilerSuggestion = description: string; }; -export type CompilerErrorDetailOptions = { +export type PlainCompilerErrorDetailOptions = { + errorCode?: void; reason: string; description?: string | null | undefined; - severity: ErrorSeverity; + severity: + | ErrorSeverity.Invariant + | ErrorSeverity.Todo + | ErrorSeverity.InvalidConfig; loc: SourceLocation | null; suggestions?: Array | null | undefined; }; +export type CodedCompilerErrorDetailOptions = { + errorCode: ErrorCode; + description?: string | null | undefined; + loc: SourceLocation | null; + suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; +}; + +export type CompilerErrorDetailOptions = + | PlainCompilerErrorDetailOptions + | CodedCompilerErrorDetailOptions; export type PrintErrorMessageOptions = { /** @@ -102,19 +91,72 @@ export type PrintErrorMessageOptions = { eslint: boolean; }; +export function makeCompilerDiagnostic( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + }, +): CompilerDiagnostic { + return makeCompilerDiagnostic(code, options); +} + export class CompilerDiagnostic { options: CompilerDiagnosticOptions; - constructor(options: CompilerDiagnosticOptions) { + /** + * Constructor is private to enforce that we either only create invariant diagnostics + * or use ErrorCodes + */ + private constructor(options: CompilerDiagnosticOptions) { this.options = options; } - static create( - options: Omit, - ): CompilerDiagnostic { + static create< + T extends CompilerDiagnosticOptions & {severity: ErrorSeverity.Invariant}, + >(options: Omit): CompilerDiagnostic { return new CompilerDiagnostic({...options, details: []}); } + static fromCode( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + details?: Array | null | undefined; + }, + ): CompilerDiagnostic { + const errorEntry = ErrorCodeDetails[code]; + let description = undefined; + if (errorEntry.description != null) { + description = errorEntry.description; + } + if (options?.description != null && options.description.length > 0) { + if (description != null && description.length > 0) { + description += ' '; + } else { + description = ''; + } + description += options.description; + } + + const diagnosticOptions: CompilerDiagnosticOptions = { + severity: errorEntry.severity, + category: errorEntry.reason, + description, + linterCategory: errorEntry.linterCategory, + suggestions: options?.suggestions, + details: options?.details ?? [], + }; + + return new CompilerDiagnostic(diagnosticOptions); + } + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return {options: {...this.options, linterCategory: undefined}}; + } + get category(): CompilerDiagnosticOptions['category'] { return this.options.category; } @@ -127,6 +169,9 @@ export class CompilerDiagnostic { get suggestions(): CompilerDiagnosticOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): CompilerDiagnosticOptions['linterCategory'] { + return this.options.linterCategory; + } withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic { this.options.details.push(detail); @@ -138,11 +183,10 @@ export class CompilerDiagnostic { } printErrorMessage(source: string, options: PrintErrorMessageOptions): string { - const buffer = [ - printErrorSummary(this.severity, this.category), - '\n\n', - this.description, - ]; + const buffer = [printErrorSummary(this.severity, this.category)]; + if (this.description != null) { + buffer.push(`\n\n${this.description}`); + } for (const detail of this.options.details) { switch (detail.kind) { case 'error': { @@ -202,13 +246,65 @@ export class CompilerErrorDetail { this.options = options; } - get reason(): CompilerErrorDetailOptions['reason'] { - return this.options.reason; + static fromCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + return new CompilerErrorDetail({ + ...details, + errorCode: code, + } as CodedCompilerErrorDetailOptions); } - get description(): CompilerErrorDetailOptions['description'] { + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return { + options: { + reason: this.reason, + description: this.description, + severity: this.severity, + loc: this.loc, + suggestions: this.suggestions, + }, + }; + } + + get reason(): string { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].reason; + } else { + return this.options.reason; + } + } + get description(): string | null | undefined { + if (this.options.errorCode != null) { + let description = undefined; + if (ErrorCodeDetails[this.options.errorCode].description != null) { + description = ErrorCodeDetails[this.options.errorCode].description; + } + if ( + this.options.description != null && + this.options.description.length > 0 + ) { + if (description != null && description.length > 0) { + description += '. '; + } else { + description = ''; + } + description += this.options.description; + } + return description; + } return this.options.description; } - get severity(): CompilerErrorDetailOptions['severity'] { + get severity(): ErrorSeverity { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].severity; + } return this.options.severity; } get loc(): CompilerErrorDetailOptions['loc'] { @@ -217,6 +313,12 @@ export class CompilerErrorDetail { get suggestions(): CompilerErrorDetailOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): LinterCategory | null | undefined { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].linterCategory; + } + return undefined; + } primaryLocation(): SourceLocation | null { return this.loc; @@ -266,7 +368,7 @@ export class CompilerError extends Error { static invariant( condition: unknown, - options: Omit, + options: Omit, ): asserts condition { if (!condition) { const errors = new CompilerError(); @@ -280,14 +382,14 @@ export class CompilerError extends Error { } } - static throwDiagnostic(options: CompilerDiagnosticOptions): never { + static throwDiagnostic(diagnostic: CompilerDiagnostic): never { const errors = new CompilerError(); - errors.pushDiagnostic(new CompilerDiagnostic(options)); + errors.pushDiagnostic(diagnostic); throw errors; } static throwTodo( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -296,34 +398,21 @@ export class CompilerError extends Error { throw errors; } - static throwInvalidJS( - options: Omit, + static throwFromCode( + code: ErrorCode, + options?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, ): never { const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidJS, - }), - ); - throw errors; - } - - static throwInvalidReact( - options: Omit, - ): never { - const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidReact, - }), - ); + errors.pushErrorDetail(CompilerErrorDetail.fromCode(code, options)); throw errors; } static throwInvalidConfig( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -392,13 +481,10 @@ export class CompilerError extends Error { } push(options: CompilerErrorDetailOptions): CompilerErrorDetail { - const detail = new CompilerErrorDetail({ - reason: options.reason, - description: options.description ?? null, - severity: options.severity, - suggestions: options.suggestions, - loc: typeof options.loc === 'symbol' ? null : options.loc, - }); + if (options instanceof CompilerErrorDetail) { + return this.pushErrorDetail(options); + } + const detail = new CompilerErrorDetail(options); return this.pushErrorDetail(detail); } @@ -407,6 +493,19 @@ export class CompilerError extends Error { return detail; } + pushErrorCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + const detail = CompilerErrorDetail.fromCode(code, details); + this.details.push(detail); + return detail; + } + hasErrors(): boolean { return this.details.length > 0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 24ce37cf72..ea1d28a62e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -38,10 +38,10 @@ export function validateRestrictedImports( ImportDeclaration(importDeclPath) { if (restrictedImports.has(importDeclPath.node.source.value)) { error.push({ - severity: ErrorSeverity.Todo, reason: 'Bailing out due to blocklisted import', description: `Import from module ${importDeclPath.node.source.value}`, loc: importDeclPath.node.loc ?? null, + severity: ErrorSeverity.Todo, }); } }, @@ -205,10 +205,10 @@ export class ProgramContext { } const error = new CompilerError(); error.push({ - severity: ErrorSeverity.Todo, reason: 'Encountered conflicting global in generated program', description: `Conflict from local binding ${name}`, loc: scope.getBinding(name)?.path.node.loc ?? null, + severity: ErrorSeverity.Todo, suggestions: null, }); return Err(error); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c13940ed10..1a8e1457ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerErrorDetail, - CompilerErrorDetailOptions, + PlainCompilerErrorDetailOptions, } from '../CompilerError'; import { EnvironmentConfig, @@ -107,6 +107,8 @@ export type PluginOptions = { * passes. * * Defaults to false + * + * TODO: rename this to lintOnly or something similar */ noEmit: boolean; @@ -234,7 +236,10 @@ export type CompileErrorEvent = { export type CompileDiagnosticEvent = { kind: 'CompileDiagnostic'; fnLoc: t.SourceLocation | null; - detail: Omit, 'suggestions'>; + detail: Omit< + Omit, + 'suggestions' + >; }; export type CompileSuccessEvent = { kind: 'CompileSuccess'; 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 a4f984f195..7a004fa18c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -100,7 +100,6 @@ import {outlineJSX} from '../Optimization/OutlineJsx'; import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls'; import {transformFire} from '../Transform'; import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender'; -import {CompilerError} from '..'; import {validateStaticComponents} from '../Validation/ValidateStaticComponents'; import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions'; import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects'; @@ -174,7 +173,7 @@ function runWithEnvironment( !env.config.disableMemoizationForDebugging && !env.config.enableChangeDetectionForDebugging ) { - dropManualMemoization(hir).unwrap(); + env.logOrThrowErrors(dropManualMemoization(hir)); log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } @@ -207,10 +206,10 @@ function runWithEnvironment( if (env.isInferredMemoEnabled) { if (env.config.validateHooksUsage) { - validateHooksUsage(hir).unwrap(); + env.logOrThrowErrors(validateHooksUsage(hir)); } - if (env.config.validateNoCapitalizedCalls) { - validateNoCapitalizedCalls(hir).unwrap(); + if (env.config.validateNoCapitalizedCalls != null) { + env.logOrThrowErrors(validateNoCapitalizedCalls(hir)); } } @@ -230,20 +229,17 @@ function runWithEnvironment( log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); if (!env.config.enableNewMutationAliasingModel) { - const fnEffectErrors = inferReferenceEffects(hir); + const fnEffectResult = inferReferenceEffects(hir); + if (env.isInferredMemoEnabled) { - if (fnEffectErrors.length > 0) { - CompilerError.throw(fnEffectErrors[0]); - } + env.logOrThrowErrors(fnEffectResult); } log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); } else { const mutabilityAliasingErrors = inferMutationAliasingEffects(hir); log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } + env.logOrThrowErrors(mutabilityAliasingErrors); } } @@ -272,10 +268,8 @@ function runWithEnvironment( }); log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } - validateLocalsNotReassignedAfterRender(hir); + env.logOrThrowErrors(mutabilityAliasingErrors.map(() => undefined)); + env.logOrThrowErrors(validateLocalsNotReassignedAfterRender(hir)); } } @@ -285,15 +279,15 @@ function runWithEnvironment( } if (env.config.validateRefAccessDuringRender) { - validateNoRefAccessInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoRefAccessInRender(hir)); } if (env.config.validateNoSetStateInRender) { - validateNoSetStateInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoSetStateInRender(hir)); } if (env.config.validateNoDerivedComputationsInEffects) { - validateNoDerivedComputationsInEffects(hir); + env.logOrThrowErrors(validateNoDerivedComputationsInEffects(hir)); } if (env.config.validateNoSetStateInEffects) { @@ -305,14 +299,14 @@ function runWithEnvironment( } if (env.config.validateNoImpureFunctionsInRender) { - validateNoImpureFunctionsInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoImpureFunctionsInRender(hir)); } if ( env.config.validateNoFreezingKnownMutableFunctions || env.config.enableNewMutationAliasingModel ) { - validateNoFreezingKnownMutableFunctions(hir).unwrap(); + env.logOrThrowErrors(validateNoFreezingKnownMutableFunctions(hir)); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 79bbee37a5..825f83a2a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -7,11 +7,7 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, ErrorSeverity} from '../CompilerError'; import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; @@ -32,6 +28,7 @@ import { } from './Suppression'; import {GeneratedSource} from '../HIR'; import {Err, Ok, Result} from '../Utils/Result'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; export type CompilerPass = { opts: PluginOptions; @@ -101,12 +98,9 @@ function findDirectivesDynamicGating( if (t.isValidIdentifier(maybeMatch[1])) { result.push({directive, match: maybeMatch[1]}); } else { - errors.push({ - reason: `Dynamic gating directive is not a valid JavaScript identifier`, + errors.pushErrorCode(ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, { description: `Found '${directive.value.value}'`, - severity: ErrorSeverity.InvalidReact, loc: directive.loc ?? null, - suggestions: null, }); } } @@ -115,14 +109,11 @@ function findDirectivesDynamicGating( return Err(errors); } else if (result.length > 1) { const error = new CompilerError(); - error.push({ - reason: `Multiple dynamic gating directives found`, + error.pushErrorCode(ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, { description: `Expected a single directive but found [${result .map(r => r.directive.value.value) .join(', ')}]`, - severity: ErrorSeverity.InvalidReact, loc: result[0].directive.loc ?? null, - suggestions: null, }); return Err(error); } else if (result.length === 1) { @@ -451,14 +442,12 @@ export function compileProgram( if (programContext.hasModuleScopeOptOut) { if (compiledFns.length > 0) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: - 'Unexpected compiled functions when module scope opt-out is present', - severity: ErrorSeverity.Invariant, - loc: null, - }), - ); + error.push({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }); handleError(error, programContext, null); } return null; @@ -591,9 +580,7 @@ function processFn( let compiledFn: CodegenFunction; const compileResult = tryCompileFunction(fn, fnType, programContext); if (compileResult.kind === 'error') { - if (directives.optOut != null) { - logError(compileResult.error, programContext, fn.node.loc ?? null); - } else { + if (directives.optOut == null) { handleError(compileResult.error, programContext, fn.node.loc ?? null); } const retryResult = retryCompileFunction(fn, fnType, programContext); @@ -692,7 +679,7 @@ function tryCompileFunction( fn, programContext.opts.environment, fnType, - 'all_features', + programContext.opts.noEmit ? 'lint_only' : 'all_features', programContext, programContext.opts.logger, programContext.filename, @@ -805,15 +792,7 @@ function shouldSkipCompilation( if (pass.opts.sources) { if (pass.filename === null) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: `Expected a filename but found none.`, - description: - "When the 'sources' config options is specified, the React compiler will only compile files with a name", - severity: ErrorSeverity.InvalidConfig, - loc: null, - }), - ); + error.pushErrorCode(ErrorCode.FILENAME_NOT_SET); handleError(error, pass, null); return true; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts index ee341b111f..75828aa108 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, - ErrorSeverity, + ErrorCode, } from '../CompilerError'; import {assertExhaustive} from '../Utils/utils'; import {GeneratedSource} from '../HIR'; @@ -165,14 +165,12 @@ export function suppressionsToCompilerError( let reason, suggestion; switch (suppressionRange.source) { case 'Eslint': - reason = - 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled'; + reason = ErrorCode.BAILOUT_ESLINT_SUPPRESSION; suggestion = 'Remove the ESLint suppression and address the React error'; break; case 'Flow': - reason = - 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow'; + reason = ErrorCode.BAILOUT_FLOW_SUPPRESSION; suggestion = 'Remove the Flow suppression and address the React error'; break; default: @@ -182,10 +180,8 @@ export function suppressionsToCompilerError( ); } error.pushDiagnostic( - CompilerDiagnostic.create({ - category: reason, - description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``, - severity: ErrorSeverity.InvalidReact, + CompilerDiagnostic.fromCode(reason, { + description: `Found suppression \`${suppressionRange.disableComment.value.trim()}\``, suggestions: [ { description: suggestion, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 16d7c3713c..5271d66886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -8,27 +8,24 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..'; +import {CompilerError, EnvironmentConfig, Logger} from '..'; import {getOrInsertWith} from '../Utils/utils'; import {Environment, GeneratedSource} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; import {CompileProgramMetadata} from './Program'; -import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError'; +import {CompilerDiagnostic} from '../CompilerError'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; -function throwInvalidReact( - options: Omit, +function logAndThrowDiagnostic( + diagnostic: CompilerDiagnostic, {logger, filename}: TraversalState, ): never { - const detail: CompilerDiagnosticOptions = { - severity: ErrorSeverity.InvalidReact, - ...options, - }; logger?.logEvent(filename, { kind: 'CompileError', fnLoc: null, - detail: new CompilerDiagnostic(detail), + detail: diagnostic, }); - CompilerError.throwDiagnostic(detail); + CompilerError.throwDiagnostic(diagnostic); } function isAutodepsSigil( @@ -90,10 +87,8 @@ function assertValidEffectImportReference( * as it may have already been transformed by the compiler (and not * memoized). */ - throwInvalidReact( - { - category: - 'Cannot infer dependencies of this effect. This will break your build!', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.DID_NOT_INFER_DEPS, { description: 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' + (maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''), @@ -104,7 +99,7 @@ function assertValidEffectImportReference( loc: parent.node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } @@ -121,10 +116,8 @@ function assertValidFireImportReference( paths[0], context.transformErrors, ); - throwInvalidReact( - { - category: - '[Fire] Untransformed reference to compiler-required feature.', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.CANNOT_COMPILE_FIRE, { description: 'Either remove this `fire` call or ensure it is successfully transformed by the compiler' + maybeErrorDiagnostic @@ -137,7 +130,7 @@ function assertValidFireImportReference( loc: paths[0].node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts b/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts index fa3f551ff5..85d6bd0457 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts @@ -1,4 +1,5 @@ import {CompilerError, SourceLocation} from '..'; +import {ErrorCode} from '../CompilerError'; import { ConcreteType, printConcrete, @@ -12,8 +13,8 @@ export function unsupportedLanguageFeature( desc: string, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Typedchecker does not currently support language feature: ${desc}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Typedchecker does not currently support language feature: ${desc}`, loc, }); } @@ -49,16 +50,16 @@ export function raiseUnificationErrors( loc, }); } else if (errs.length === 1) { - CompilerError.throwInvalidJS({ - reason: `Unable to unify types because ${printUnificationError(errs[0])}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to unify types because ${printUnificationError(errs[0])}`, loc, }); } else { const messages = errs .map(err => `\t* ${printUnificationError(err)}`) .join('\n'); - CompilerError.throwInvalidJS({ - reason: `Unable to unify types because:\n${messages}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to unify types because:\n${messages}`, loc, }); } @@ -69,21 +70,21 @@ export function unresolvableTypeVariable( id: VariableId, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Unable to resolve free variable ${id} to a concrete type`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to resolve free variable ${id} to a concrete type`, loc, }); } export function cannotAddVoid(explicit: boolean, loc: SourceLocation): never { if (explicit) { - CompilerError.throwInvalidJS({ - reason: `Undefined is not a valid operand of \`+\``, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Undefined is not a valid operand of \`+\``, loc, }); } else { - CompilerError.throwInvalidJS({ - reason: `Value may be undefined, which is not a valid operand of \`+\``, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Value may be undefined, which is not a valid operand of \`+\``, loc, }); } @@ -93,8 +94,8 @@ export function unsupportedTypeAnnotation( desc: string, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Typedchecker does not currently support type annotation: ${desc}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Typedchecker does not currently support type annotation: ${desc}`, loc, }); } @@ -106,16 +107,16 @@ export function checkTypeArgumentArity( loc: SourceLocation, ): void { if (expected !== actual) { - CompilerError.throwInvalidJS({ - reason: `Expected ${desc} to have ${expected} type parameters, got ${actual}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Expected ${desc} to have ${expected} type parameters, got ${actual}`, loc, }); } } export function notAFunction(desc: string, loc: SourceLocation): void { - CompilerError.throwInvalidJS({ - reason: `Cannot call ${desc} because it is not a function`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Cannot call ${desc} because it is not a function`, loc, }); } @@ -124,8 +125,8 @@ export function notAPolymorphicFunction( desc: string, loc: SourceLocation, ): void { - CompilerError.throwInvalidJS({ - reason: `Cannot call ${desc} with type arguments because it is not a polymorphic function`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Cannot call ${desc} with type arguments because it is not a polymorphic function`, loc, }); } 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 3b11670146..26f05f58ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -12,6 +12,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, + ErrorCode, ErrorSeverity, } from '../CompilerError'; import {Err, Ok, Result} from '../Utils/Result'; @@ -170,14 +171,12 @@ export function lower( ); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.Todo, - category: `Handle ${param.node.type} parameters`, + CompilerDiagnostic.fromCode(ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, { description: `[BuildHIR] Add support for ${param.node.type} parameters.`, }).withDetail({ kind: 'error', loc: param.node.loc ?? null, - message: 'Unsupported parameter type', + message: `Unsupported parameter type: ${param.node.type}`, }), ); } @@ -201,14 +200,12 @@ export function lower( directives = body.get('directives').map(d => d.node.value.value); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidJS, - category: `Unexpected function body kind`, + CompilerDiagnostic.fromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`, }).withDetail({ kind: 'error', loc: body.node.loc ?? null, - message: 'Expected a block statement or expression', + message: 'Change this to a block statement or expression', }), ); } @@ -769,12 +766,12 @@ function lowerStatement( const testExpr = case_.get('test'); if (testExpr.node == null) { if (hasDefault) { - builder.errors.push({ - reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, - severity: ErrorSeverity.InvalidJS, - loc: case_.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + { + loc: case_.node.loc ?? null, + }, + ); break; } hasDefault = true; @@ -886,19 +883,20 @@ function lowerStatement( if (builder.isContextIdentifier(id)) { if (kind === InstructionKind.Const) { const declRangeStart = declaration.parentPath.node.start!; - builder.errors.push({ - reason: `Expect \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: id.node.loc ?? null, - suggestions: [ - { - description: 'Change to a `let` declaration', - op: CompilerSuggestionOperation.Replace, - range: [declRangeStart, declRangeStart + 5], // "const".length - text: 'let', - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: id.node.loc ?? null, + suggestions: [ + { + description: 'Change to a `let` declaration', + op: CompilerSuggestionOperation.Replace, + range: [declRangeStart, declRangeStart + 5], // "const".length + text: 'let', + }, + ], + }, + ); } lowerValueToTemporary(builder, { kind: 'DeclareContext', @@ -932,13 +930,13 @@ function lowerStatement( } } } else { - builder.errors.push({ - reason: `Expected variable declaration to be an identifier if no initializer was provided`, - description: `Got a \`${id.type}\``, - severity: ErrorSeverity.InvalidJS, - loc: stmt.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + { + description: `Got a \`${id.type}\``, + loc: stmt.node.loc ?? null, + }, + ); } } return; @@ -1374,12 +1372,8 @@ function lowerStatement( return; } case 'WithStatement': { - builder.errors.push({ - reason: `JavaScript 'with' syntax is not supported`, - description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_WITH, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1394,12 +1388,8 @@ function lowerStatement( * and complex enough to support that we don't anticipate supporting anytime soon. Developers * are encouraged to lift classes out of component/hook declarations. */ - builder.errors.push({ - reason: 'Inline `class` declarations are not supported', - description: `Move class declarations outside of components/hooks`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_INNER_CLASS, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1423,12 +1413,8 @@ function lowerStatement( case 'ImportDeclaration': case 'TSExportAssignment': case 'TSImportEqualsDeclaration': { - builder.errors.push({ - reason: - 'JavaScript `import` and `export` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_IMPORT_EXPORT, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1438,12 +1424,8 @@ function lowerStatement( return; } case 'TSNamespaceExportDeclaration': { - builder.errors.push({ - reason: - 'TypeScript `namespace` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_TS_NAMESPACE, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1698,12 +1680,8 @@ function lowerExpression( const expr = exprPath as NodePath; const calleePath = expr.get('callee'); if (!calleePath.isExpression()) { - builder.errors.push({ - reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, - description: `Got a \`${calleePath.node.type}\``, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_NEW_EXPRESSION, { loc: calleePath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -1800,12 +1778,12 @@ function lowerExpression( last = lowerExpressionToTemporary(builder, item); } if (last === null) { - builder.errors.push({ - reason: `Expected sequence expression to have at least one expression`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + { + loc: expr.node.loc ?? null, + }, + ); } else { lowerValueToTemporary(builder, { kind: 'StoreLocal', @@ -2289,18 +2267,18 @@ function lowerExpression( }); for (const [name, locations] of Object.entries(fbtLocations)) { if (locations.length > 1) { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support duplicate fbt tags', - description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, - details: locations.map(loc => { - return { - kind: 'error', - message: `Multiple \`<${tagName}:${name}>\` tags found`, - loc, - }; + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_DUPLICATE_FBT_TAGS, { + description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, + details: locations.map(loc => { + return { + kind: 'error', + message: `Multiple \`<${tagName}:${name}>\` tags found`, + loc, + }; + }), }), - }); + ); } } } @@ -2389,11 +2367,8 @@ function lowerExpression( const quasis = expr.get('quasis'); if (subexprs.length !== quasis.length - 1) { - builder.errors.push({ - reason: `Unexpected quasi and subexpression lengths in template literal`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_QUASI_LENGTHS, { loc: exprPath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -2441,24 +2416,23 @@ function lowerExpression( }; } } else { - builder.errors.push({ - reason: `Only object properties can be deleted`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: [ - { - description: 'Remove this line', - range: [expr.node.start!, expr.node.end!], - op: CompilerSuggestionOperation.Remove, - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + { + loc: expr.node.loc ?? null, + suggestions: [ + { + description: 'Remove this line', + range: [expr.node.start!, expr.node.end!], + op: CompilerSuggestionOperation.Remove, + }, + ], + }, + ); return {kind: 'UnsupportedNode', node: expr.node, loc: exprLoc}; } } else if (expr.node.operator === 'throw') { - builder.errors.push({ - reason: `Throw expressions are not supported`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_THROW_EXPRESSION, { loc: expr.node.loc ?? null, suggestions: [ { @@ -3285,10 +3259,8 @@ function lowerJsxElementName( const name = exprPath.node.name.name; const tag = `${namespace}:${name}`; if (namespace.indexOf(':') !== -1 || name.indexOf(':') !== -1) { - builder.errors.push({ - reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + builder.errors.pushErrorCode(ErrorCode.INVALID_JSX_NAMESPACED_NAME, { description: `Got \`${namespace}\` : \`${name}\``, - severity: ErrorSeverity.InvalidJS, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3583,11 +3555,7 @@ function lowerIdentifier( } default: { if (binding.kind === 'Global' && binding.name === 'eval') { - builder.errors.push({ - reason: `The 'eval' function is not supported`, - description: - 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_EVAL, { loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3653,9 +3621,7 @@ function lowerIdentifierForAssignment( binding.bindingKind === 'const' && kind === InstructionKind.Reassign ) { - builder.errors.push({ - reason: `Cannot reassign a \`const\` variable`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, { loc: path.node.loc ?? null, description: binding.identifier.name != null @@ -3710,12 +3676,13 @@ function lowerAssignment( let temporary; if (builder.isContextIdentifier(lvalue)) { if (kind === InstructionKind.Const && !isHoistedIdentifier) { - builder.errors.push({ - reason: `Expected \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: lvalue.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: lvalue.node.loc ?? null, + suggestions: null, + }, + ); } if ( @@ -3726,7 +3693,8 @@ function lowerAssignment( ) { builder.errors.push({ reason: `Unexpected context variable kind`, - severity: ErrorSeverity.InvalidJS, + description: `Expected one of Const, Reassign, Let, Function, got ${kind}`, + severity: ErrorSeverity.Invariant, loc: lvalue.node.loc ?? null, suggestions: null, }); 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 957c5ab84a..ad1815a62d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -93,7 +93,7 @@ export const MacroSchema = z.union([ z.tuple([z.string(), z.array(MacroMethodSchema)]), ]); -export type CompilerMode = 'all_features' | 'no_inferred_memo'; +export type CompilerMode = 'all_features' | 'no_inferred_memo' | 'lint_only'; export type Macro = z.infer; export type MacroMethod = z.infer; @@ -829,6 +829,14 @@ export class Environment { } } + logOrThrowErrors(errors: Result): void { + if (this.compilerMode === 'lint_only') { + this.logErrors(errors); + } else { + errors.unwrap(); + } + } + isContextIdentifier(node: t.Identifier): boolean { return this.#contextIdentifiers.has(node); } 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 51715b3c1e..89b2c0fc41 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -15,6 +15,7 @@ import {Type, makeType} from './Types'; import {z} from 'zod'; import type {AliasingEffect} from '../Inference/AliasingEffects'; import {isReservedWord} from '../Utils/Keyword'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -1322,18 +1323,18 @@ export function forkTemporaryIdentifier( */ export function makeIdentifierName(name: string): ValidatedIdentifier { if (isReservedWord(name)) { - CompilerError.throwInvalidJS({ - reason: 'Expected a non-reserved identifier name', - loc: GeneratedSource, - description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, - suggestions: null, - }); + CompilerError.throwFromCode( + ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + { + loc: GeneratedSource, + description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, + }, + ); } else { CompilerError.invariant(t.isValidIdentifier(name), { reason: `Expected a valid identifier name`, loc: GeneratedSource, description: `\`${name}\` is not a valid JavaScript identifier`, - suggestions: null, }); } return { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 81959ea361..1d71452d0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -7,7 +7,7 @@ import {Binding, NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {Environment} from './Environment'; import { BasicBlock, @@ -37,6 +37,7 @@ import { mapTerminalSuccessors, terminalFallthrough, } from './visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -308,19 +309,17 @@ export default class HIRBuilder { resolveBinding(node: t.Identifier): Identifier { if (node.name === 'fbt') { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support local variables named `fbt`', - description: - 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', - details: [ - { - kind: 'error', - message: 'Rename to avoid conflict with fbt plugin', - loc: node.loc ?? GeneratedSource, - }, - ], - }); + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, { + details: [ + { + kind: 'error', + message: 'Rename to avoid conflict with fbt plugin', + loc: node.loc ?? GeneratedSource, + }, + ], + }), + ); } const originalName = node.name; let name = originalName; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 7aeb3edb22..fa9bddd3f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -5,12 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, - SourceLocation, -} from '..'; +import {CompilerDiagnostic, CompilerError, SourceLocation} from '..'; import { CallExpression, Effect, @@ -35,6 +30,7 @@ import { makeInstructionId, } from '../HIR'; import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; type ManualMemoCallee = { @@ -299,12 +295,11 @@ function extractManualMemoizationArgs( >; if (fnPlace == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected a callback function to be passed to ${kind}`, - description: `Expected a callback function to be passed to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + kind === 'useMemo' + ? ErrorCode.INVALID_USE_MEMO_NO_ARG0 + : ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Expected a callback function to be passed to ${kind}`, @@ -314,12 +309,11 @@ function extractManualMemoizationArgs( } if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Unexpected spread argument to ${kind}`, - description: `Unexpected spread argument to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + fnPlace.kind === 'Spread' + ? ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT + : ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Unexpected spread argument to ${kind}`, @@ -334,12 +328,9 @@ function extractManualMemoizationArgs( ); if (maybeDepsList == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list for ${kind} to be an array literal`, - description: `Expected the dependency list for ${kind} to be an array literal`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + ).withDetail({ kind: 'error', loc: depsListPlace.loc, message: `Expected the dependency list for ${kind} to be an array literal`, @@ -352,12 +343,9 @@ function extractManualMemoizationArgs( const maybeDep = sidemap.maybeDeps.get(dep.identifier.id); if (maybeDep == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + ).withDetail({ kind: 'error', loc: dep.loc, message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, @@ -457,16 +445,16 @@ export function dropManualMemoization( if (funcToCheck !== undefined && funcToCheck.loweredFunc.func) { if (!hasNonVoidReturn(funcToCheck.loweredFunc.func)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks must return a value', - description: `This ${ - manualMemo.loadInstr.value.kind === 'PropertyLoad' - ? 'React.useMemo' - : 'useMemo' - } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + { + description: `This ${ + manualMemo.loadInstr.value.kind === 'PropertyLoad' + ? 'React.useMemo' + : 'useMemo' + } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, + }, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: 'useMemo() callbacks must return a value', @@ -497,12 +485,9 @@ export function dropManualMemoization( */ if (!sidemap.functions.has(fnPlace.identifier.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the first argument to be an inline function expression`, - description: `Expected the first argument to be an inline function expression`, - suggestions: [], - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + ).withDetail({ kind: 'error', loc: fnPlace.loc, message: `Expected the first argument to be an inline function expression`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts index a01ca188a0..5fcf8a3cae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts @@ -25,6 +25,7 @@ import { isRefOrRefValue, } from '../HIR'; import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {assertExhaustive} from '../Utils/utils'; interface State { @@ -62,22 +63,20 @@ function inferOperandEffect(state: State, place: Place): null | FunctionEffect { // We ignore mutations of primitives since this is not a React-specific problem value.kind !== ValueKind.Primitive ) { - let reason = getWriteErrorReason(value); + let errorCode = getWriteErrorReason(value); return { kind: value.reason.size === 1 && value.reason.has(ValueReason.Global) ? 'GlobalMutation' : 'ReactMutation', error: { - reason, + errorCode, description: place.identifier.name !== null && place.identifier.name.kind === 'named' ? `Found mutation of \`${place.identifier.name.value}\`` : null, loc: place.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }; } @@ -266,11 +265,8 @@ export function inferInstructionFunctionEffects( functionEffects.push({ kind: 'GlobalMutation', error: { - reason: - 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + errorCode: ErrorCode.INVALID_WRITE_GLOBAL, loc: instr.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }); break; @@ -324,28 +320,28 @@ function isEffectSafeOutsideRender(effect: FunctionEffect): boolean { return effect.kind === 'GlobalMutation'; } -export function getWriteErrorReason(abstractValue: AbstractValue): string { +export function getWriteErrorReason(abstractValue: AbstractValue): ErrorCode { if (abstractValue.reason.has(ValueReason.Global)) { - return 'Modifying a variable defined outside a component or hook is not allowed. Consider using an effect'; + return ErrorCode.INVALID_WRITE_GLOBAL; } else if (abstractValue.reason.has(ValueReason.JsxCaptured)) { - return 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX'; + return ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX; } else if (abstractValue.reason.has(ValueReason.Context)) { - return `Modifying a value returned from 'useContext()' is not allowed.`; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT; } else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) { - return 'Modifying a value returned from a function whose return value should not be mutated'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE; } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) { - return 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS; } else if (abstractValue.reason.has(ValueReason.State)) { - return "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead"; + return ErrorCode.INVALID_WRITE_STATE; } else if (abstractValue.reason.has(ValueReason.ReducerState)) { - return "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead"; + return ErrorCode.INVALID_WRITE_REDUCER_STATE; } else if (abstractValue.reason.has(ValueReason.Effect)) { - return 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()'; + return ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY; } else if (abstractValue.reason.has(ValueReason.HookCaptured)) { - return 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook'; + return ErrorCode.INVALID_WRITE_HOOK_CAPTURED; } else if (abstractValue.reason.has(ValueReason.HookReturn)) { - return 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed'; + return ErrorCode.INVALID_WRITE_HOOK_RETURN; } else { - return 'This modifies a variable that React considers immutable'; + return ErrorCode.INVALID_WRITE_GENERIC; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index 2adf78fe05..60146d8335 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -9,7 +9,6 @@ import { CompilerDiagnostic, CompilerError, Effect, - ErrorSeverity, SourceLocation, ValueKind, } from '..'; @@ -69,6 +68,7 @@ import {getWriteErrorReason} from './InferFunctionEffects'; import prettyFormat from 'pretty-format'; import {createTemporaryPlace} from '../HIR/HIRBuilder'; import {AliasingEffect, AliasingSignature, hashEffect} from './AliasingEffects'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; const DEBUG = false; @@ -442,7 +442,7 @@ function applySignature( const value = state.kind(effect.value); switch (value.kind) { case ValueKind.Frozen: { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -455,10 +455,9 @@ function applySignature( effects.push({ kind: 'MutateFrozen', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -1026,22 +1025,20 @@ function applyEffect( const hoistedAccess = context.hoistedContextDeclarations.get( effect.value.identifier.declarationId, ); - const diagnostic = CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access variable before it is declared', - description: `${variable ?? 'This variable'} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.`, - }); + const diagnostic = CompilerDiagnostic.fromCode( + ErrorCode.INVALID_ACCESS_BEFORE_INIT, + ); if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) { diagnostic.withDetail({ kind: 'error', loc: hoistedAccess.loc, - message: `${variable ?? 'variable'} accessed before it is declared`, + message: `${variable ?? 'This variable'} is accessed before it is declared`, }); } diagnostic.withDetail({ kind: 'error', loc: effect.value.loc, - message: `${variable ?? 'variable'} is declared here`, + message: `${variable ?? 'This variable'} is declared here`, }); applyEffect( @@ -1056,7 +1053,7 @@ function applyEffect( effects, ); } else { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -1075,10 +1072,9 @@ function applyEffect( ? 'MutateFrozen' : 'MutateGlobal', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -2006,15 +2002,12 @@ function computeSignatureForInstruction( effects.push({ kind: 'MutateGlobal', place: value.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'Cannot reassign variables declared outside of the component/hook', - description: `Variable ${variable} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)`, - }).withDetail({ + error: CompilerDiagnostic.fromCode( + ErrorCode.INVALID_WRITE_GLOBAL, + ).withDetail({ kind: 'error', loc: instr.loc, - message: `${variable} cannot be reassigned`, + message: `${variable} should not be reassigned`, }), }); effects.push({kind: 'Assign', from: value.value, into: lvalue}); @@ -2105,19 +2098,16 @@ function computeEffectsForLegacySignature( effects.push({ kind: 'Impure', place: receiver, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - }).withDetail({ - kind: 'error', - loc, - message: 'Cannot call impure function', - }), + error: CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail( + { + kind: 'error', + loc, + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', + }, + ), }); } const stores: Array = []; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 1b0856791a..a806ebd4bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError'; +import {CompilerError} from '../CompilerError'; import {Environment} from '../HIR'; import { AbstractValue, @@ -48,6 +48,7 @@ import { eachTerminalOperand, eachTerminalSuccessor, } from '../HIR/visitors'; +import {Err, Ok, Result} from '../Utils/Result'; import {assertExhaustive, Set_isSuperset} from '../Utils/utils'; import { inferTerminalFunctionEffects, @@ -106,7 +107,7 @@ const UndefinedValue: InstructionValue = { export default function inferReferenceEffects( fn: HIRFunction, options: {isFunctionExpression: boolean} = {isFunctionExpression: false}, -): Array { +): Result { /* * Initial state contains function params * TODO: include module declarations here as well @@ -247,10 +248,17 @@ export default function inferReferenceEffects( if (options.isFunctionExpression) { fn.effects = functionEffects; - return []; } else { - return transformFunctionEffectErrors(functionEffects); + const errors = transformFunctionEffectErrors(functionEffects); + if (errors.length > 0) { + const compilerError = new CompilerError(); + for (const detail of errors) { + compilerError.push(detail); + } + return Err(compilerError); + } } + return Ok(void 0); } type FreezeAction = {values: Set; reason: Set}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts index f88c85f2f0..dbb84944b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts @@ -42,6 +42,7 @@ import { import {eachInstructionOperand} from '../HIR/visitors'; import {printSourceLocationLine} from '../HIR/PrintHIR'; import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * TODO(jmbrown): @@ -50,8 +51,6 @@ import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; * - React.useEffect calls */ -const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`'; - export function transformFire(fn: HIRFunction): void { const context = new Context(fn.env); replaceFireFunctions(fn, context); @@ -178,9 +177,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } else if (value.args.length > 1 && value.args[1].kind === 'Spread') { @@ -188,9 +185,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].place.loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -243,11 +238,9 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } else { context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description: '`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed', - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else { @@ -262,10 +255,8 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else if (value.kind === 'CallExpression') { @@ -394,9 +385,7 @@ function ensureNoRemainingCalleeCaptures( description: `All uses of ${calleeName} must be either used with a fire() call in \ this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \ ${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -411,9 +400,7 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { context.pushError({ loc: place.identifier.loc, description: 'Cannot use `fire` outside of a useEffect function', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts new file mode 100644 index 0000000000..2fd3244fc5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts @@ -0,0 +1,611 @@ +import {ErrorSeverity} from './CompilerErrorSeverity'; + +export enum LinterCategory { + RULES_OF_HOOKS = 'rules-of-hooks', + IMPURE_FUNCTIONS = 'impure-functions', + JSX_IN_TRY = 'jsx-in-try', + NO_REF_ACCESS_IN_RENDER = 'no-ref-access-in-render', + VALIDATE_MANUAL_MEMO = 'validate-manual-memo', + DYNAMIC_MANUAL_MEMO = 'dynamic-manual-memo', + + EXHAUSTIVE_DEPS = 'exhaustive-deps', + MEMOIZED_DEPENDENCIES = 'memoized-dependencies', // not real! + INVALID_WRITE = 'invalid-write', + CAPITALIZED_CALLS = 'capitalized-calls', + STATIC_COMPONENTS = 'static-components', + NO_SET_STATE_IN_RENDER = 'no-set-state-in-render', + NO_SET_STATE_IN_EFFECTS = 'no-set-state-in-effects', + UNNECESSARY_EFFECTS = 'unnecessary-effects', + + UNSUPPORTED_SYNTAX = 'unsupported-syntax', + + TODO_SYNTAX = 'todo-syntax', + + COMPILER_CONFIG = 'compiler-config', +} + +export enum ErrorCode { + HOOK_CALL_STATIC, + HOOK_INVALID_REFERENCE, + HOOK_CALL_REACTIVE, + HOOK_CALL_NOT_TOP_LEVEL, + IMPURE_FUNCTIONS, + JSX_IN_TRY, + NO_REF_ACCESS_IN_RENDER, + WRITE_AFTER_RENDER, + REASSIGN_IN_ASYNC, + INVALID_WRITE, + CAPITALIZED_CALLS, + STATIC_COMPONENTS, + INVALID_USE_MEMO_CALLBACK_RETURN, + INVALID_USE_MEMO_CALLBACK_PARAMETERS, + INVALID_USE_MEMO_CALLBACK_ASYNC, + INVALID_USE_MEMO_NO_ARG0, + INVALID_USE_CALLBACK_NO_ARG0, + DYNAMIC_MANUAL_MEMO_CALLBACK, + DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + + INVALID_SET_STATE_IN_RENDER, + INVALID_SET_STATE_IN_MEMO, + INVALID_SET_STATE_IN_EFFECTS, + + NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + + DYNAMIC_GATING_IS_NOT_IDENTIFIER, + DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + FILENAME_NOT_SET, + + INVALID_WRITE_GLOBAL, + INVALID_WRITE_FROZEN_VALUE_JSX, + INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + INVALID_WRITE_IMMUTABLE_ARGS, + INVALID_WRITE_STATE, + INVALID_WRITE_REDUCER_STATE, + INVALID_WRITE_EFFECT_DEPENDENCY, + INVALID_WRITE_HOOK_CAPTURED, + INVALID_WRITE_HOOK_RETURN, + INVALID_ACCESS_BEFORE_INIT, + INVALID_WRITE_GENERIC, + + MEMOIZED_EFFECT_DEPENDENCIES, + + INVALID_SYNTAX_MULTIPLE_DEFAULTS, + INVALID_SYNTAX_REASSIGNED_CONST, + INVALID_SYNTAX_BAD_VARIABLE_DECL, + UNSUPPORTED_WITH, + UNSUPPORTED_INNER_CLASS, + INVALID_IMPORT_EXPORT, + INVALID_TS_NAMESPACE, + UNSUPPORTED_NEW_EXPRESSION, + UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + INVALID_QUASI_LENGTHS, + INVALID_SYNTAX_DELETE_EXPRESSION, + UNSUPPORTED_THROW_EXPRESSION, + INVALID_JSX_NAMESPACED_NAME, + UNSUPPORTED_EVAL, + INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + INVALID_JAVASCRIPT_AST, + BAILOUT_ESLINT_SUPPRESSION, + BAILOUT_FLOW_SUPPRESSION, + MANUAL_MEMO_MUTATED_LATER, + MANUAL_MEMO_REMOVED, + MANUAL_MEMO_DEPENDENCIES_CONFLICT, + + CANNOT_COMPILE_FIRE, + DID_NOT_INFER_DEPS, + + /** Todo syntax */ + TODO_CONFLICTING_FBT_IDENTIFIER, + TODO_DUPLICATE_FBT_TAGS, + UNKNOWN_FUNCTION_PARAMETERS, +} + +type ErrorCodeType = { + code: ErrorCode; + description?: string; + severity: ErrorSeverity; + reason: string; + linterCategory: LinterCategory | null; +}; + +export const ErrorCodeDetails: Record = { + [ErrorCode.HOOK_CALL_STATIC]: { + code: ErrorCode.HOOK_CALL_STATIC, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_INVALID_REFERENCE]: { + code: ErrorCode.HOOK_INVALID_REFERENCE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_REACTIVE]: { + code: ErrorCode.HOOK_CALL_REACTIVE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_NOT_TOP_LEVEL]: { + code: ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.IMPURE_FUNCTIONS]: { + code: ErrorCode.IMPURE_FUNCTIONS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot call impure functions during render', + description: + 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).', + linterCategory: LinterCategory.IMPURE_FUNCTIONS, + }, + [ErrorCode.JSX_IN_TRY]: { + code: ErrorCode.JSX_IN_TRY, + severity: ErrorSeverity.InvalidReact, + reason: 'Avoid constructing JSX within try/catch', + description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, + linterCategory: LinterCategory.JSX_IN_TRY, + }, + [ErrorCode.NO_REF_ACCESS_IN_RENDER]: { + code: ErrorCode.NO_REF_ACCESS_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access refs during render', + description: + 'React refs are values that are not needed for rendering. Refs should only be accessed ' + + 'outside of render, such as in event handlers or effects. ' + + 'Accessing a ref value (the `current` property) during render can cause your component ' + + 'not to update as expected (https://react.dev/reference/react/useRef)', + linterCategory: LinterCategory.NO_REF_ACCESS_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_EFFECTS]: { + code: ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Calling setState synchronously within an effect can trigger cascading renders', + description: + 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + + 'In general, the body of an effect should do one or both of the following:\n' + + '* Update external systems with the latest state from React.\n' + + '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + + 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + + '(https://react.dev/learn/you-might-not-need-an-effect)', + linterCategory: LinterCategory.NO_SET_STATE_IN_EFFECTS, + }, + [ErrorCode.WRITE_AFTER_RENDER]: { + code: ErrorCode.WRITE_AFTER_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot modify local variables after render completes', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.REASSIGN_IN_ASYNC]: { + code: ErrorCode.REASSIGN_IN_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variable in async function', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE]: { + code: ErrorCode.INVALID_WRITE, + severity: ErrorSeverity.InvalidReact, + reason: 'This value cannot be modified', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.CAPITALIZED_CALLS]: { + code: ErrorCode.CAPITALIZED_CALLS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config', + linterCategory: LinterCategory.CAPITALIZED_CALLS, + }, + [ErrorCode.STATIC_COMPONENTS]: { + code: ErrorCode.STATIC_COMPONENTS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot create components during render', + linterCategory: LinterCategory.STATIC_COMPONENTS, + }, + [ErrorCode.INVALID_USE_MEMO_NO_ARG0]: { + code: ErrorCode.INVALID_USE_MEMO_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useMemo`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_CALLBACK_NO_ARG0]: { + code: ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useCallback`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useMemo', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + + [ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useCallback', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not accept parameters', + description: + 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not be async or generator functions', + description: + 'useMemo() callbacks are called once and must synchronously return a value.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks must return a value', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the first argument to be an inline function expression`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list of useMemo or useCallback to be an array literal`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY]: { + code: ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_SET_STATE_IN_RENDER]: { + code: ErrorCode.INVALID_SET_STATE_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState during render may trigger an infinite loop', + description: + 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_MEMO]: { + code: ErrorCode.INVALID_SET_STATE_IN_MEMO, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState from useMemo may trigger an infinite loop', + description: + 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS]: { + code: ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', + linterCategory: LinterCategory.UNNECESSARY_EFFECTS, + }, + + /** Invalid writes */ + [ErrorCode.INVALID_WRITE_GLOBAL]: { + code: ErrorCode.INVALID_WRITE_GLOBAL, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variables declared outside of the component/hook', + description: + 'Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX]: { + code: ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + severity: ErrorSeverity.InvalidReact, + reason: `Modifying a value returned from 'useContext()' is not allowed.`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a function whose return value should not be mutated', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_STATE]: { + code: ErrorCode.INVALID_WRITE_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_REDUCER_STATE]: { + code: ErrorCode.INVALID_WRITE_REDUCER_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY]: { + code: ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_CAPTURED]: { + code: ErrorCode.INVALID_WRITE_HOOK_CAPTURED, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_RETURN]: { + code: ErrorCode.INVALID_WRITE_HOOK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_ACCESS_BEFORE_INIT]: { + code: ErrorCode.INVALID_ACCESS_BEFORE_INIT, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access variable before it is declared', + description: `Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_GENERIC]: { + code: ErrorCode.INVALID_WRITE_GENERIC, + severity: ErrorSeverity.InvalidReact, + reason: 'This modifies a variable that React considers immutable', + linterCategory: LinterCategory.INVALID_WRITE, + }, + + /** Compiler Config */ + [ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER]: { + code: ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, + severity: ErrorSeverity.InvalidReact, + reason: 'Dynamic gating directive is not a valid JavaScript identifier', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES]: { + code: ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + severity: ErrorSeverity.InvalidReact, + reason: 'Expected a single dynamic gating directive', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.FILENAME_NOT_SET]: { + code: ErrorCode.FILENAME_NOT_SET, + severity: ErrorSeverity.InvalidConfig, + reason: `Expected a filename but found none.`, + description: + "When the 'sources' config options is specified, the React compiler will only compile files with a name", + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + + /** Effect dependencies */ + [ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES]: { + code: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, + reason: + 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.MEMOIZED_DEPENDENCIES, + }, + + /** Invalid / unsupported syntax */ + [ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS]: { + code: ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST]: { + code: ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + reason: `Expect \`const\` declaration not to be reassigned`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL]: { + code: ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + reason: `Expected variable declaration to be an identifier if no initializer was provided`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_WITH]: { + code: ErrorCode.UNSUPPORTED_WITH, + reason: `JavaScript 'with' syntax is not supported`, + description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_INNER_CLASS]: { + code: ErrorCode.UNSUPPORTED_INNER_CLASS, + reason: 'Inline `class` declarations are not supported', + description: `Move class declarations outside of components/hooks`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_IMPORT_EXPORT]: { + code: ErrorCode.INVALID_IMPORT_EXPORT, + reason: + 'JavaScript `import` and `export` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_TS_NAMESPACE]: { + code: ErrorCode.INVALID_TS_NAMESPACE, + reason: + 'TypeScript `namespace` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_NEW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_NEW_EXPRESSION, + reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + reason: `Expected sequence expression to have at least one expression`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_QUASI_LENGTHS]: { + code: ErrorCode.INVALID_QUASI_LENGTHS, + reason: `Unexpected quasi and subexpression lengths in template literal`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION]: { + code: ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + reason: `Only object properties can be deleted`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_THROW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_THROW_EXPRESSION, + reason: `Throw expressions are not supported`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JSX_NAMESPACED_NAME]: { + code: ErrorCode.INVALID_JSX_NAMESPACED_NAME, + reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EVAL]: { + code: ErrorCode.UNSUPPORTED_EVAL, + reason: `The 'eval' function is not supported`, + description: + 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME]: { + code: ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + reason: 'Expected a non-reserved identifier name', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JAVASCRIPT_AST]: { + code: ErrorCode.INVALID_JAVASCRIPT_AST, + reason: 'Encountered invalid JavaScript', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.BAILOUT_ESLINT_SUPPRESSION]: { + code: ErrorCode.BAILOUT_ESLINT_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_REMOVED]: { + code: ErrorCode.MANUAL_MEMO_REMOVED, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + + /** + * This is left vague as fire is very experimental + */ + [ErrorCode.CANNOT_COMPILE_FIRE]: { + code: ErrorCode.CANNOT_COMPILE_FIRE, + reason: 'Cannot compile `fire`', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.DID_NOT_INFER_DEPS]: { + code: ErrorCode.DID_NOT_INFER_DEPS, + reason: + 'Cannot infer dependencies of this effect. This will break your build!', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + [ErrorCode.BAILOUT_FLOW_SUPPRESSION]: { + code: ErrorCode.BAILOUT_FLOW_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + /** Syntax React Compiler may eventually support */ + [ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER]: { + code: ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, + reason: 'Support local variables named `fbt`', + description: + 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.TODO_DUPLICATE_FBT_TAGS]: { + code: ErrorCode.TODO_DUPLICATE_FBT_TAGS, + reason: 'Support duplicate fbt tags', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.UNKNOWN_FUNCTION_PARAMETERS]: { + code: ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, + severity: ErrorSeverity.Todo, + reason: 'Currently unsupported function parameter syntax', + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_MUTATED_LATER]: { + code: ErrorCode.MANUAL_MEMO_MUTATED_LATER, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: [ + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', + 'This dependency may be mutated later, which could cause the value to change unexpectedly.', + ].join(''), + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT]: { + code: ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + '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.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts new file mode 100644 index 0000000000..f399cadb9d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts @@ -0,0 +1,34 @@ +export enum ErrorSeverity { + /** + * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some + * misunderstanding on the user’s part. + */ + InvalidJS = 'InvalidJS', + /** + * JS syntax that is not supported and which we do not plan to support. Developers should + * rewrite to use supported forms. + */ + UnsupportedJS = 'UnsupportedJS', + /** + * Code that breaks the rules of React. + */ + InvalidReact = 'InvalidReact', + /** + * Incorrect configuration of the compiler. + */ + InvalidConfig = 'InvalidConfig', + /** + * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve + * memoization. + */ + CannotPreserveMemoization = 'CannotPreserveMemoization', + /** + * Unhandled syntax that we don't support yet. + */ + Todo = 'Todo', + /** + * An unexpected internal error in the compiler that indicates critical issues that can panic + * the compiler. + */ + Invariant = 'Invariant', +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts index b28228339c..d04e163960 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts @@ -6,11 +6,7 @@ */ import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, CompilerErrorDetail} from '../CompilerError'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {isHookName} from '../HIR/Environment'; import { @@ -27,6 +23,7 @@ import { } from '../HIR/visitors'; import {assertExhaustive} from '../Utils/utils'; import {Result} from '../Utils/Result'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; /** * Represents the possible kinds of value which may be stored at a given Place during @@ -111,8 +108,6 @@ export function validateHooksUsage( // Once a particular hook has a conditional call error, don't report any further issues for this hook setKind(place, Kind.Error); - const reason = - 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)'; const previousError = typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined; @@ -120,15 +115,15 @@ export function validateHooksUsage( * In some circumstances such as optional calls, we may first encounter a "hook may not be referenced as normal values" error. * If that same place is also used as a conditional call, upgrade the error to a conditonal hook error */ - if (previousError === undefined || previousError.reason !== reason) { + if ( + previousError === undefined || + previousError.reason !== + ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason + ) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason, + CompilerErrorDetail.fromCode(ErrorCode.HOOK_CALL_STATIC, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -139,13 +134,8 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + CompilerErrorDetail.fromCode(ErrorCode.HOOK_INVALID_REFERENCE, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -156,14 +146,12 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', - loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }), + CompilerErrorDetail.fromCode( + ErrorCodeDetails[ErrorCode.HOOK_CALL_REACTIVE].code, + { + loc: place.loc, + }, + ), ); } } @@ -424,7 +412,7 @@ export function validateHooksUsage( } for (const [, error] of errorsByPlace) { - errors.push(error); + errors.pushErrorDetail(error); } return errors.asResult(); } @@ -446,16 +434,10 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void { : instr.value.property; const hookKind = getHookKind(fn.env, callee.identifier); if (hookKind != null) { - errors.pushErrorDetail( - new CompilerErrorDetail({ - severity: ErrorSeverity.InvalidReact, - reason: - 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', - loc: callee.loc, - description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, - suggestions: null, - }), - ); + errors.pushErrorCode(ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, { + loc: callee.loc, + description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, + }); } break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts index 31bbf8c94d..b6c3038915 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; import {HIRFunction, IdentifierId, Place} from '../HIR'; import { eachInstructionLValue, @@ -13,13 +13,17 @@ import { eachTerminalOperand, } from '../HIR/visitors'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Ok, Result} from '../Utils/Result'; /** * Validates that local variables cannot be reassigned after render. * This prevents a category of bugs in which a closure captures a * binding from one render but does not update */ -export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { +export function validateLocalsNotReassignedAfterRender( + fn: HIRFunction, +): Result { const contextVariables = new Set(); const reassignment = getContextReassignment( fn, @@ -35,9 +39,7 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable after render completes', + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER, { description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }).withDetail({ kind: 'error', @@ -45,8 +47,9 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { message: `Cannot reassign ${variable} after render completes`, }), ); - throw errors; + return errors.asResult(); } + return Ok(undefined); } function getContextReassignment( @@ -90,9 +93,7 @@ function getContextReassignment( ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable in async function', + CompilerDiagnostic.fromCode(ErrorCode.REASSIGN_IN_ASYNC, { description: 'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead', }).withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts index b33cfb1512..90c714c557 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity} from '..'; +import {CompilerError} from '..'; import { Identifier, Instruction, @@ -22,6 +22,7 @@ import { ReactiveFunctionVisitor, visitReactiveFunction, } from '../ReactiveScopes/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -108,12 +109,8 @@ class Visitor extends ReactiveFunctionVisitor { isUnmemoized(deps.identifier, this.scopes)) ) { state.push({ - reason: - 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', - description: null, - severity: ErrorSeverity.CannotPreserveMemoization, + errorCode: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null, - suggestions: null, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts index 8989cb1ac2..72e237f660 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..'; +import {CompilerError, EnvironmentConfig} from '..'; import {HIRFunction, IdentifierId} from '../HIR'; import {DEFAULT_GLOBALS} from '../HIR/Globals'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateNoCapitalizedCalls( @@ -33,8 +34,6 @@ export function validateNoCapitalizedCalls( const errors = new CompilerError(); const capitalLoadGlobals = new Map(); const capitalizedProperties = new Map(); - const reason = - 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config'; for (const [, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { switch (value.kind) { @@ -55,11 +54,9 @@ export function validateNoCapitalizedCalls( const calleeIdentifier = value.callee.identifier.id; const calleeName = capitalLoadGlobals.get(calleeIdentifier); if (calleeName != null) { - CompilerError.throwInvalidReact({ - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${calleeName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; @@ -78,12 +75,9 @@ export function validateNoCapitalizedCalls( const propertyIdentifier = value.property.identifier.id; const propertyName = capitalizedProperties.get(propertyIdentifier); if (propertyName != null) { - errors.push({ - severity: ErrorSeverity.InvalidReact, - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${propertyName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts index d026a94ed4..5ec74f0dae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity, SourceLocation} from '..'; +import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, BlockId, @@ -19,6 +19,8 @@ import { eachInstructionValueOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Result} from '../Utils/Result'; /** * Validates that useEffect is not used for derived computations which could/should @@ -43,7 +45,9 @@ import { * const fullName = firstName + ' ' + lastName; * ``` */ -export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { +export function validateNoDerivedComputationsInEffects( + fn: HIRFunction, +): Result { const candidateDependencies: Map = new Map(); const functions: Map = new Map(); const locals: Map = new Map(); @@ -96,9 +100,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { } } } - if (errors.hasErrors()) { - throw errors; - } + return errors.asResult(); } function validateEffect( @@ -218,13 +220,6 @@ function validateEffect( } for (const loc of setStateLocations) { - errors.push({ - reason: - 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', - description: null, - severity: ErrorSeverity.InvalidReact, - loc, - suggestions: null, - }); + errors.pushErrorCode(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, {loc}); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts index 7a79c74780..f829e4b92a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts @@ -5,7 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; +import {ErrorCode} from '../CompilerError'; import { FunctionEffect, HIRFunction, @@ -65,9 +66,7 @@ export function validateNoFreezingKnownMutableFunctions( ? `\`${place.identifier.name.value}\`` : 'a local variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot modify local variables after render completes', + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER, { description: `This argument is a function which may reassign or mutate ${variable} after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts index 85adb79ceb..ee452fc08a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {HIRFunction} from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -35,19 +36,13 @@ export function validateNoImpureFunctionsInRender( ); if (signature != null && signature.impure === true) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail({ kind: 'error', loc: callee.loc, - message: 'Cannot call impure function', + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts index eea6c0a08e..bab00d7159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts @@ -5,8 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {BlockId, HIRFunction} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -35,11 +36,7 @@ export function validateNoJSXInTryStatement( case 'JsxExpression': case 'JsxFragment': { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Avoid constructing JSX within try/catch', - description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.JSX_IN_TRY).withDetail({ kind: 'error', loc: value.loc, message: 'Avoid constructing JSX within try/catch', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts index e1c17625f4..1ffc5d013c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { BlockId, HIRFunction, @@ -26,6 +22,7 @@ import { eachPatternOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Err, Ok, Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -467,11 +464,9 @@ function validateNoRefAccessInRenderImpl( if (fnType.fn.readRefEffect) { didError = true; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.NO_REF_ACCESS_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, message: `This function accesses a ref value`, @@ -730,15 +725,13 @@ function destructure( function guardCheck(errors: CompilerError, operand: Place, env: Env): void { if (env.get(operand.identifier.id)?.kind === 'Guard') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -754,15 +747,13 @@ function validateNoRefValueAccess( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -780,15 +771,13 @@ function validateNoRefPassedToFunction( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Passing a ref to a function may read its value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Passing a ref to a function may read its value during render`, + }, + ), ); } } @@ -802,15 +791,13 @@ function validateNoRefUpdate( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'Ref' || type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Cannot update ref during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Cannot update ref during render`, + }, + ), ); } } @@ -823,21 +810,13 @@ function validateNoDirectRefValueAccess( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: type.loc ?? operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: type.loc ?? operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } - -const ERROR_DESCRIPTION = - 'React refs are values that are not needed for rendering. Refs should only be accessed ' + - 'outside of render, such as in event handlers or effects. ' + - 'Accessing a ref value (the `current` property) during render can cause your component ' + - 'not to update as expected (https://react.dev/reference/react/useRef)'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts index 9c4efa380c..0a5638277d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { HIRFunction, IdentifierId, @@ -20,6 +16,7 @@ import { Place, } from '../HIR'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -95,19 +92,9 @@ export function validateNoSetStateInEffects( const setState = setStateFunctions.get(arg.identifier.id); if (setState !== undefined) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState synchronously within an effect can trigger cascading renders', - description: - 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + - 'In general, the body of an effect should do one or both of the following:\n' + - '* Update external systems with the latest state from React.\n' + - '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + - 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + - '(https://react.dev/learn/you-might-not-need-an-effect)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + ).withDetail({ kind: 'error', loc: setState.loc, message: diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts index 81209d61c6..dc67540ee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts @@ -5,14 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, isSetStateType} from '../HIR'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -127,14 +124,9 @@ function validateNoSetStateInRenderImpl( ) { if (activeManualMemoId !== null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState from useMemo may trigger an infinite loop', - description: - 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_MEMO, + ).withDetail({ kind: 'error', loc: callee.loc, message: 'Found setState() within useMemo()', @@ -142,17 +134,12 @@ function validateNoSetStateInRenderImpl( ); } else if (unconditionalBlocks.has(block.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState during render may trigger an infinite loop', - description: - 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, - message: 'Found setState() within useMemo()', + message: 'Found setState() call here', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index 6bb59247da..7776e15cbb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError, ErrorCode} from '../CompilerError'; import { DeclarationId, Effect, @@ -280,13 +276,8 @@ function validateInferredDep( } } errorState.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - '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. ', + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, { + description: DEBUG || // If the dependency is a named variable then we can report it. Otherwise only print in debug mode (dep.identifier.name != null && dep.identifier.name.kind === 'named') @@ -300,9 +291,6 @@ function validateInferredDep( : 'Inferred dependency not present in source' }.` : '', - ] - .join('') - .trim(), suggestions: null, }).withDetail({ kind: 'error', @@ -534,15 +522,9 @@ class Visitor extends ReactiveFunctionVisitor { !this.prunedScopes.has(identifier.scope.id) ) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', - 'This dependency may be mutated later, which could cause the value to change unexpectedly.', - ].join(''), - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.MANUAL_MEMO_MUTATED_LATER, + ).withDetail({ kind: 'error', loc, message: 'This dependency may be modified later', @@ -582,18 +564,10 @@ class Visitor extends ReactiveFunctionVisitor { for (const identifier of decls) { if (isUnmemoized(identifier, this.scopes)) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. ', - DEBUG - ? `${printIdentifier(identifier)} was not memoized.` - : '', - ] - .join('') - .trim(), + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_REMOVED, { + description: DEBUG + ? `${printIdentifier(identifier)} was not memoized.` + : '', }).withDetail({ kind: 'error', loc, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts index 7f5fb408b4..12722fa2d5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, SourceLocation} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -64,9 +61,7 @@ export function validateStaticComponents( ); if (location != null) { error.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot create components during render', + CompilerDiagnostic.fromCode(ErrorCode.STATIC_COMPONENTS, { description: `Components created during render will reset their state each time they are created. Declare components outside of render. `, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts index 69ab401c89..05531ff211 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateUseMemo(fn: HIRFunction): Result { @@ -73,13 +70,9 @@ export function validateUseMemo(fn: HIRFunction): Result { ? firstParam.loc : firstParam.place.loc; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks may not accept parameters', - description: - 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + ).withDetail({ kind: 'error', loc, message: 'Callbacks with parameters are not supported', @@ -89,14 +82,9 @@ export function validateUseMemo(fn: HIRFunction): Result { if (body.loweredFunc.func.async || body.loweredFunc.func.generator) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'useMemo() callbacks may not be async or generator functions', - description: - 'useMemo() callbacks are called once and must synchronously return a value.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + ).withDetail({ kind: 'error', loc: body.loc, message: 'Async and generator functions are not supported', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md index ce42e65125..e47107723f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-component-tag-function.ts:3:4 1 | function Component() { 2 | const Foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | return ; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md index ee57ea6eb0..5acfcde730 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md @@ -22,13 +22,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-jsx-children.ts:3:4 1 | function Component() { 2 | const foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | // Children are generally access/called during render, so 6 | // modifying a global in a children function is almost diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md index 8476885de7..eb2516e386 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md @@ -18,13 +18,15 @@ function Component() { ``` Found 1 error: -Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.assign-global-in-jsx-spread-attribute.ts:4:4 2 | function Component() { 3 | const foo = () => { > 4 | someGlobal = true; - | ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + | ^^^^^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | return
; 7 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md index 6e522e1666..a5530a1180 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md @@ -20,7 +20,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `$FlowFixMe[react-rule-hook]` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `$FlowFixMe[react-rule-hook]` error.bailout-on-flow-suppression.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index 3221f97731..b348bc6191 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -23,7 +23,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable my-app/react-rule` error.bailout-on-suppression-of-custom-rule.ts:3:0 1 | // @eslintSuppressionRules:["my-app","react-rule"] @@ -36,7 +36,7 @@ error.bailout-on-suppression-of-custom-rule.ts:3:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line my-app/react-rule` error.bailout-on-suppression-of-custom-rule.ts:7:2 5 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md index 6e9887c5ac..d6e0a07d7a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md @@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md index e5c28e6e36..71f96f1ab5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md @@ -19,7 +19,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md index a8a83f6b11..ef6f6091a6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md @@ -17,7 +17,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `callback` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md index 4b49c5f653..96485fb584 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md @@ -17,12 +17,12 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `x` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-assignment-to-global.ts:2:3 1 | function useFoo(props) { > 2 | [x] = props; - | ^ `x` cannot be reassigned + | ^ `x` should not be reassigned 3 | return {x}; 4 | } 5 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md index 6da3b558bd..bae0df94af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-to-local-global-variables.ts:3:6 1 | function Component(props) { 2 | let a; > 3 | [a, b] = props.value; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 4 | 5 | return [a, b]; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md index 8e8b7917d7..f3f2cff6e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md @@ -39,13 +39,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-global-reassignment-indirect.ts:9:4 7 | 8 | const setGlobal = () => { > 9 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 10 | }; 11 | const indirectSetGlobal = () => { 12 | setGlobal(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md index 291d3873b4..8c512ae350 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md @@ -42,13 +42,13 @@ Found 1 error: Error: Cannot access variable before it is declared -`setState` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized error.invalid-hoisting-setstate.ts:19:18 17 | * $2 = Function context=setState 18 | */ > 19 | useEffect(() => setState(2), []); - | ^^^^^^^^ `setState` accessed before it is declared + | ^^^^^^^^ `setState` is accessed before it is declared 20 | 21 | const [state, setState] = useState(0); 22 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md index 3155d64329..cf6b33ce8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md index 4ac7af5bae..768e0300bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md @@ -23,7 +23,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.invalid-mutation-of-possible-props-phi-indirect.ts:4:4 2 | let x = cond ? someGlobal : props.foo; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md index 437fbcb38c..de59216e60 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md @@ -48,7 +48,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md index 25b9a5c186..72bfa49422 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md @@ -15,7 +15,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign a `const` variable +Error: Expect `const` declaration not to be reassigned `x` is declared as const. @@ -23,7 +23,7 @@ error.invalid-reassign-const.ts:3:2 1 | function Component() { 2 | const x = 0; > 3 | x = 1; - | ^ Cannot reassign a `const` variable + | ^ Expect `const` declaration not to be reassigned 4 | } 5 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md index 6379515a05..1b5942449b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md @@ -17,7 +17,7 @@ function useFoo() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md index 368b312022..8a27203097 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md @@ -49,7 +49,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md index 8c7973377d..309fd02906 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md @@ -50,7 +50,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index 3ecbcc97c3..1b4f26b2c2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -43,7 +43,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md index 96be8584be..14d6068b97 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ @@ -32,7 +32,7 @@ error.invalid-sketchy-code-use-forget.ts:1:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:5:2 3 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md index e19cee7532..d77327b90d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md @@ -40,7 +40,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-unclosed-eslint-suppression.ts:2:0 1 | // Note: Everything below this is sketchy diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md index fa9e20a418..f10764dc70 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md @@ -29,7 +29,7 @@ error.invalid-unconditional-set-state-in-render.ts:6:2 4 | const aliased = setX; 5 | > 6 | setX(1); - | ^^^^ Found setState() within useMemo() + | ^^^^ Found setState() call here 7 | aliased(2); 8 | 9 | return x; @@ -42,7 +42,7 @@ error.invalid-unconditional-set-state-in-render.ts:7:2 5 | 6 | setX(1); > 7 | aliased(2); - | ^^^^^^^ Found setState() within useMemo() + | ^^^^^^^ Found setState() call here 8 | 9 | return x; 10 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md index 337b9dd30c..db7b07261e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md @@ -34,7 +34,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `a` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md index 78530caf4a..9903cf2a1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.mutate-property-from-global.ts:4:9 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md index c7b1ac5f45..508aea8d27 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:5:4 3 | function Component(props) { @@ -34,7 +34,7 @@ error.not-useEffect-external-mutate.ts:5:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md index 89bcedf956..606c1c42c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md @@ -24,13 +24,15 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Modifying a variable defined outside a component or hook is not allowed. Consider using an effect +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.object-capture-global-mutation.ts:4:4 2 | function Foo() { 3 | const x = () => { > 4 | window.href = 'foo'; - | ^^^^^^ Modifying a variable defined outside a component or hook is not allowed. Consider using an effect + | ^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | const y = {x}; 7 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md index 2c409ea5b5..cd7f202a1c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md @@ -28,13 +28,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassign-global-fn-arg.ts:5:4 3 | export default function MyApp() { 4 | const fn = () => { > 5 | b = 2; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 6 | }; 7 | return foo(fn); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md index 8835f19ad1..6963940109 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md @@ -21,26 +21,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:4:4 2 | const foo = () => { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | }; 7 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | }; 7 | foo(); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md index 4d259dd8d4..88ae3a7ae8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md @@ -18,26 +18,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:3:2 1 | function Component() { 2 | // Cannot assign to globals > 3 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 4 | moduleLocal = true; 5 | } 6 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | // Cannot assign to globals 3 | someUnknownGlobal = true; > 4 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 5 | } 6 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md index 9c87cafff1..0bcfae2e9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md @@ -24,7 +24,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` error.sketchy-code-exhaustive-deps.ts:6:7 4 | () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md index 7077b733b0..bd1b3ed4c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md @@ -25,7 +25,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.sketchy-code-rules-of-hooks.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md index 7ffe3f84cf..544532b3b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.store-property-in-global.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md index a88d43b352..f4d192530e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md @@ -19,7 +19,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `onClick` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md index 3d54bcd75c..6f9da8fda9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-after-loop-break.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md index c892066bf8..6ab4ecb36e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md @@ -27,7 +27,7 @@ error.unconditional-set-state-in-render-after-loop.ts:6:2 4 | for (const _ of props) { 5 | } > 6 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 7 | return state; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md index a617a2f572..69c81c3ff8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-with-loop-throw.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md index dfb5c7b56f..79369f3608 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md @@ -30,7 +30,7 @@ error.unconditional-set-state-lambda.ts:8:2 6 | setX(1); 7 | }; > 8 | foo(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 9 | 10 | return [x]; 11 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md index f03b514c3f..c8a775499a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md @@ -38,7 +38,7 @@ error.unconditional-set-state-nested-function-expressions.ts:16:2 14 | bar(); 15 | }; > 16 | baz(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 17 | 18 | return [x]; 19 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md index 8432be198b..cfd27c7356 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md @@ -23,13 +23,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `renderCount` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.update-global-should-bailout.ts:3:2 1 | let renderCount = 0; 2 | function useFoo() { > 3 | renderCount += 1; - | ^^^^^^^^^^^^^^^^ `renderCount` cannot be reassigned + | ^^^^^^^^^^^^^^^^ `renderCount` should not be reassigned 4 | return renderCount; 5 | } 6 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md index 188814ee02..1531425c20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md @@ -30,9 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Expected the dependency list for useMemo to be an array literal - -Expected the dependency list for useMemo to be an array literal +Error: Expected the dependency list of useMemo or useCallback to be an array literal error.useMemo-non-literal-depslist.ts:10:4 8 | return text.toUpperCase(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md index d8250c6f57..b4f642df43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Expected a single dynamic gating directive","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md index 08eb396bb1..cf7a8cad85 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr2` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":333},"end":{"line":11,"column":43,"index":357},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":248},"end":{"line":8,"column":46,"index":292},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":277},"end":{"line":8,"column":34,"index":280},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":316},"end":{"line":11,"column":54,"index":368},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":349},"end":{"line":11,"column":42,"index":356},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index b629bfef27..0925a9b982 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -65,7 +65,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index c16890e52e..3e3135929b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -45,7 +45,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md index a9782a3b9e..a6f268555c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index babb4e8969..96b1d866fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -44,7 +44,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md index d78e4becec..bef031723c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md @@ -35,12 +35,12 @@ Found 1 error: Error: Cannot access variable before it is declared -`data` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized 9 | // TDZ violation! 10 | const onRefetch = useCallback(() => { > 11 | refetch(data); - | ^^^^ `data` accessed before it is declared + | ^^^^ `data` is accessed before it is declared 12 | }, [refetch]); 13 | 14 | // The context variable gets frozen here since it's passed to a hook diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md index 80a12e5d40..f1cc5c4808 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md @@ -22,7 +22,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | function Component(props) { @@ -35,7 +35,7 @@ error.not-useEffect-external-mutate.ts:6:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:7:4 5 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md index 41ed513912..bb4d91a4bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md @@ -22,26 +22,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | const foo = () => { 4 | // Cannot assign to globals > 5 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 6 | moduleLocal = true; 7 | }; 8 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:6:4 4 | // Cannot assign to globals 5 | someUnknownGlobal = true; > 6 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 7 | }; 8 | foo(); 9 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md index 6089255fd5..84339b0759 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md @@ -19,26 +19,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | function Component() { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | } 7 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:5:2 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | } 7 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md index 2e0c890367..8441e79925 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr2` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":365},"end":{"line":11,"column":43,"index":389},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":280},"end":{"line":8,"column":46,"index":324},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":309},"end":{"line":8,"column":34,"index":312},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":348},"end":{"line":11,"column":54,"index":400},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":381},"end":{"line":11,"column":42,"index":388},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md index 27af59e175..f433f8cb88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md @@ -24,8 +24,6 @@ Found 1 error: Error: Expected the first argument to be an inline function expression -Expected the first argument to be an inline function expression - error.validate-useMemo-named-function.ts:9:20 7 | // for now. 8 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index 006d2a49c0..bda9c3b694 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -31,7 +31,7 @@ function Component({prop1}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:4) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index 8481ed2c57..9604f69476 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -15,7 +15,7 @@ console.log(fire == null); ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index f84686bc36..02753ce345 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -32,7 +32,7 @@ function Component({props, bar}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md index 81c36a362c..cdb0726f2e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md @@ -26,7 +26,7 @@ function Component({props, bar}) { ``` Found 2 errors: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. @@ -39,7 +39,7 @@ error.invalid-outside-effect.ts:8:2 10 | useCallback(() => { 11 | fire(foo(props)); -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md index 96cea9c08f..d82f1ee1b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md @@ -27,7 +27,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md index 4dc5336ebe..a841813630 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md @@ -30,7 +30,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts new file mode 100644 index 0000000000..c3d6704504 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts @@ -0,0 +1,32 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoImpureFunctionCallsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no impure function calls rule', NoImpureFunctionCallsRule, { + valid: [], + invalid: [ + { + name: 'Known impure function calls are caught', + code: normalizeIndent` + function Component() { + const date = Date.now(); + const now = performance.now(); + const rand = Math.random(); + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts new file mode 100644 index 0000000000..9192087b31 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts @@ -0,0 +1,85 @@ +/** + * 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 {RulesOfHooksRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('rules-of-hooks', RulesOfHooksRule, { + valid: [ + { + name: 'Basic example', + code: normalizeIndent` + function Component() { + useHook(); + return
Hello world
; + } + `, + }, + { + name: 'Violation with Flow suppression', + code: ` + // Valid since error already suppressed with flow. + function useHook() { + if (cond) { + // $FlowFixMe[react-rule-hook] + useConditionalHook(); + } + } + `, + }, + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function useConditional() { + cond ?? useConditionalHook(); + props.cond && useConditionalHook(); + return
Hello world
; + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts new file mode 100644 index 0000000000..8463e860df --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts @@ -0,0 +1,31 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoAmbiguousJsxRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ambiguous JSX rule', NoAmbiguousJsxRule, { + valid: [], + invalid: [ + { + name: 'JSX in try blocks are warned against', + code: normalizeIndent` + function Component(props) { + let el; + try { + el = ; + } catch { + return null; + } + return el; + } + `, + errors: [makeTestCaseError(ErrorCode.JSX_IN_TRY)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts new file mode 100644 index 0000000000..821cab8007 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {NoCapitalizedCallsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidCapitalizedCallsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('no-capitalized-calls', NoCapitalizedCallsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + import Child from './Child'; + function Component() { + return <> + {Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Method call violation', + code: normalizeIndent` + import myModule from './MyModule'; + function Component() { + return <> + {myModule.Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + import Child1 from './Child1'; + import MyModule from './MyModule'; + function Component() { + return <> + {Child1()} + {MyModule.Child2()} + ; + }`, + errors: [ + invalidCapitalizedCallsRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts new file mode 100644 index 0000000000..a8498d303f --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts @@ -0,0 +1,27 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoRefAccessInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ref access in render rule', NoRefAccessInRenderRule, { + valid: [], + invalid: [ + { + name: 'validate against simple ref access in render', + code: normalizeIndent` + function Component(props) { + const ref = useRef(null); + const value = ref.current; + return value; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_REF_ACCESS_IN_RENDER)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts new file mode 100644 index 0000000000..43ec6ad010 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts @@ -0,0 +1,48 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoSetStateInEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in effects rule', NoSetStateInEffectsRule, { + valid: [], + invalid: [ + { + name: 'unconditional setState in useEffect', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + setState(s => s + 1); + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + { + name: 'conditional setState in render', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + if (state % 2 === 0) { + setState(state + 1); + } + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts new file mode 100644 index 0000000000..77ce895339 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {ValidateSetStateInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in render rule', ValidateSetStateInRenderRule, { + valid: [], + invalid: [ + { + name: 'setState in useMemo', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + return item; + }, [cond, item, init]); + + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'unconditional setState in render', + code: normalizeIndent` + function Component(props) { + const [x, setX] = useState(0); + const aliased = setX; + + setX(1); + aliased(2); + + return x; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts new file mode 100644 index 0000000000..77f6dd93fb --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {NoUnusedDirectivesRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule} from './shared-utils'; + +testRule('no unused directives rule', NoUnusedDirectivesRule, { + valid: [], + invalid: [ + { + name: "Unused 'use no forget' directive is reported when no errors are present on components", + code: normalizeIndent` + function Component() { + 'use no forget'; + return
Hello world
+ } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction Component() {\n \n return
Hello world
\n}\n', + }, + ], + }, + ], + }, + + { + name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", + code: normalizeIndent` + function notacomponent() { + 'use no forget'; + return 1 + 1; + } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', + }, + ], + }, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts new file mode 100644 index 0000000000..c0e0cf07c9 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts @@ -0,0 +1,172 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + testRule, + makeTestCaseError, + TestRecommendedRules, +} from './shared-utils'; +import {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; + +testRule('plugin-recommended', TestRecommendedRules, { + valid: [ + { + name: 'Basic example with component syntax', + code: normalizeIndent` + export default component HelloWorld( + text: string = 'Hello!', + onClick: () => void, + ) { + return
{text}
; + } + `, + }, + + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Multiple diagnostic kinds from the same function are surfaced', + code: normalizeIndent` + import Child from './Child'; + function Component() { + const result = cond ?? useConditionalHook(); + return <> + {Child(result)} + ; + } + `, + errors: [ + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + { + name: 'Multiple diagnostics within the same file are surfaced', + code: normalizeIndent` + function useConditional1() { + 'use memo'; + return cond ?? useConditionalHook(); + } + function useConditional2(props) { + 'use memo'; + return props.cond && useConditionalHook(); + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + { + name: "'use no forget' does not disable eslint rule", + code: normalizeIndent` + let count = 0; + function Component() { + 'use no forget'; + return cond ?? useConditionalHook(); + + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple non-fatal useMemo diagnostics are surfaced', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + }, [cond, item, init]); + + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'Pipeline errors are reported', + code: normalizeIndent` + import useMyEffect from 'useMyEffect'; + import {AUTODEPS} from 'react'; + function Component({a}) { + 'use no memo'; + useMyEffect(() => console.log(a.b), AUTODEPS); + return
Hello world
; + } + `, + options: [ + { + environment: { + inferEffectDependencies: [ + { + function: { + source: 'useMyEffect', + importSpecifierName: 'default', + }, + autodepsIndex: 1, + }, + ], + }, + }, + ], + errors: [ + { + message: /Cannot infer dependencies of this effect/, + }, + ], + }, + ], +}); + +testRule('rules that are not enabled do not error', StaticComponentsRule, { + valid: [ + { + name: 'simple case', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + }, + ], + invalid: [], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts deleted file mode 100644 index bff40e9649..0000000000 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts +++ /dev/null @@ -1,287 +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 {ErrorSeverity} from 'babel-plugin-react-compiler/src'; -import {RuleTester as ESLintTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)![0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: ESLintTester.ValidTestCase[]; - invalid: ESLintTester.InvalidTestCase[]; -}; - -const tests: CompilerTestCases = { - valid: [ - { - name: 'Basic example', - code: normalizeIndent` - function foo(x, y) { - if (x) { - return foo(false, y); - } - return [y * 10]; - } - `, - }, - { - name: 'Violation with Flow suppression', - code: ` - // Valid since error already suppressed with flow. - function useHookWithHook() { - if (cond) { - // $FlowFixMe[react-rule-hook] - useConditionalHook(); - } - } - `, - }, - { - name: 'Basic example with component syntax', - code: normalizeIndent` - export default component HelloWorld( - text: string = 'Hello!', - onClick: () => void, - ) { - return
{text}
; - } - `, - }, - { - name: 'Unsupported syntax', - code: normalizeIndent` - function foo(x) { - var y = 1; - return y * x; - } - `, - }, - { - // OK because invariants are only meant for the compiler team's consumption - name: '[Invariant] Defined after use', - code: normalizeIndent` - function Component(props) { - let y = function () { - m(x); - }; - - let x = { a }; - m(x); - return y; - } - `, - }, - { - name: "Classes don't throw", - code: normalizeIndent` - class Foo { - #bar() {} - } - `, - }, - ], - invalid: [ - { - name: 'Reportable levels can be configured', - options: [{reportableLevels: new Set([ErrorSeverity.Todo])}], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: '[InvalidReact] ESlint suppression', - // Indentation is intentionally weird so it doesn't add extra whitespace - code: normalizeIndent` - function Component(props) { - // eslint-disable-next-line react-hooks/rules-of-hooks - return
{props.foo}
; - }`, - errors: [ - { - message: /React Compiler has skipped optimizing this component/, - suggestions: [ - { - output: normalizeIndent` - function Component(props) { - - return
{props.foo}
; - }`, - }, - ], - }, - { - message: - "Definition for rule 'react-hooks/rules-of-hooks' was not found.", - }, - ], - }, - { - name: 'Multiple diagnostics are surfaced', - options: [ - { - reportableLevels: new Set([ - ErrorSeverity.Todo, - ErrorSeverity.InvalidReact, - ]), - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - } - function Bar(props) { - props.a.b = 2; - return
{props.c}
- }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - { - message: /Modifying component props or hook arguments is not allowed/, - }, - ], - }, - { - name: 'Test experimental/unstable report all bailouts mode', - options: [ - { - reportableLevels: new Set([ErrorSeverity.InvalidReact]), - __unstable_donotuse_reportAllBailouts: true, - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: "'use no forget' does not disable eslint rule", - code: normalizeIndent` - let count = 0; - function Component() { - 'use no forget'; - count = count + 1; - return
Hello world {count}
- } - `, - errors: [ - { - message: - /Cannot reassign variables declared outside of the component\/hook/, - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on components", - code: normalizeIndent` - function Component() { - 'use no forget'; - return
Hello world
- } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction Component() {\n \n return
Hello world
\n}\n', - }, - ], - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", - code: normalizeIndent` - function notacomponent() { - 'use no forget'; - return 1 + 1; - } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', - }, - ], - }, - ], - }, - { - name: 'Pipeline errors are reported', - code: normalizeIndent` - import useMyEffect from 'useMyEffect'; - import {AUTODEPS} from 'react'; - function Component({a}) { - 'use no memo'; - useMyEffect(() => console.log(a.b), AUTODEPS); - return
Hello world
; - } - `, - options: [ - { - environment: { - inferEffectDependencies: [ - { - function: { - source: 'useMyEffect', - importSpecifierName: 'default', - }, - autodepsIndex: 1, - }, - ], - }, - }, - ], - errors: [ - { - message: /Cannot infer dependencies of this effect/, - }, - ], - }, - ], -}; - -const eslintTester = new ESLintTester({ - parser: require.resolve('hermes-eslint'), - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, -}); -eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts index 5a2bea6852..87baf724e1 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts @@ -6,22 +6,11 @@ */ import {RuleTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)[0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: RuleTester.ValidTestCase[]; - invalid: RuleTester.InvalidTestCase[]; -}; +import { + CompilerTestCases, + normalizeIndent, + TestRecommendedRules, +} from './shared-utils'; const tests: CompilerTestCases = { valid: [ @@ -70,6 +59,7 @@ const tests: CompilerTestCases = { }; const eslintTester = new RuleTester({ + // @ts-ignore[2353] - outdated types parser: require.resolve('@typescript-eslint/parser'), }); -eslintTester.run('react-compiler', ReactCompilerRule, tests); +eslintTester.run('react-compiler', TestRecommendedRules, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts new file mode 100644 index 0000000000..b2f4b54994 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts @@ -0,0 +1,44 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidStaticComponentsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('static-components', StaticComponentsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function Example(props) { + const Component = new ComponentFactory(); + return ; + } + `, + errors: [invalidStaticComponentsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function Example(props) { + const Component1 = new ComponentFactory(); + const Component2 = new ComponentFactory(); + return <> + + + ; + }`, + errors: [ + invalidStaticComponentsRuleErrorMessage, + invalidStaticComponentsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts new file mode 100644 index 0000000000..8d7f481f15 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts @@ -0,0 +1,36 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {UnnecessaryEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('unnecessary effects rule', UnnecessaryEffectsRule, { + valid: [], + invalid: [ + { + name: 'test case from React docs', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + // https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state + function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + + // 🔴 Avoid: redundant state and unnecessary Effect + const [fullName, setFullName] = useState(''); + useEffect(() => { + setFullName(capitalize(firstName + ' ' + lastName)); + }, [firstName, lastName]); + + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts new file mode 100644 index 0000000000..85c937a2de --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts @@ -0,0 +1,43 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {UseMemoRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('use memo rule', UseMemoRule, { + valid: [], + invalid: [ + { + name: 'Simple async violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component({a, b}) { + let x = useMemo(async () => { + await a; + }, []); + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC)], + }, + { + name: 'Simple parameters violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component() { + let x = useMemo(c => a, []); + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts new file mode 100644 index 0000000000..f9553730e4 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts @@ -0,0 +1,98 @@ +import {RuleTester as ESLintTester, Rule} from 'eslint'; +import { + ErrorCode, + ErrorCodeDetails, +} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import escape from 'regexp.escape'; +import {configs} from '../src/index'; + +/** + * A string template tag that removes padding from the left side of multi-line strings + * @param {Array} strings array of code strings (only one expected) + */ +export function normalizeIndent(strings: TemplateStringsArray): string { + const codeLines = strings[0].split('\n'); + const leftPadding = codeLines[1].match(/\s+/)![0]; + return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); +} + +export type CompilerTestCases = { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; +}; + +export const invalidHookRuleErrorMessage: ESLintTester.TestCaseError = { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason), + ), +}; + +export const invalidCapitalizedCallsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.CAPITALIZED_CALLS].reason), + ), + }; + +export const invalidStaticComponentsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.STATIC_COMPONENTS].reason), + ), + }; + +export function makeTestCaseError(code: ErrorCode): ESLintTester.TestCaseError { + return { + message: new RegExp(escape(ErrorCodeDetails[code].reason)), + }; +} + +export function testRule( + name: string, + rule: Rule.RuleModule, + tests: { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; + }, +): void { + const eslintTester = new ESLintTester({ + // @ts-ignore[2353] - outdated types + parser: require.resolve('hermes-eslint'), + parserOptions: { + ecmaVersion: 2015, + sourceType: 'module', + enableExperimentalComponentSyntax: true, + }, + }); + + eslintTester.run(name, rule, tests); +} + +/** + * Aggregates all recommended rules from the plugin. + */ +export const TestRecommendedRules: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Disallow capitalized function calls', + category: 'Possible Errors', + recommended: true, + }, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context) { + for (const rule of Object.values( + configs.recommended.plugins['react-compiler'].rules, + )) { + const listener = rule.create(context); + if (Object.entries(listener).length !== 0) { + throw new Error('TODO: handle rules that return listeners to eslint'); + } + } + return {}; + }, +}; + +test('no test', () => {}); diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index 2dd191f033..e5402611e2 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -24,11 +24,13 @@ "@babel/preset-typescript": "^7.18.6", "@babel/types": "^7.26.0", "@types/eslint": "^8.56.12", + "@types/jest": "^30.0.0", "@types/node": "^20.2.5", "babel-jest": "^29.0.3", "eslint": "8.57.0", "hermes-eslint": "^0.25.1", - "jest": "^29.5.0" + "jest": "^29.5.0", + "regexp.escape": "^2.0.1" }, "engines": { "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" diff --git a/compiler/packages/eslint-plugin-react-compiler/src/index.ts b/compiler/packages/eslint-plugin-react-compiler/src/index.ts index a3577a101e..681e9844ef 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/index.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/index.ts @@ -5,29 +5,61 @@ * LICENSE file in the root directory of this source tree. */ -import ReactCompilerRule from './rules/ReactCompilerRule'; +import * as rules from './rules/ReactCompilerRule'; const meta = { name: 'eslint-plugin-react-compiler', }; -const rules = { - 'react-compiler': ReactCompilerRule, +/** + * Validates React components and hooks follow rules of React + * and follow best practices + */ +const validationRules = { + 'rules-of-hooks': rules.RulesOfHooksRule, + 'no-capitalized-calls': rules.NoCapitalizedCallsRule, + 'no-unstable-components': rules.StaticComponentsRule, + 'validate-use-memo-callbacks': rules.UseMemoRule, + 'validate-writes': rules.InvalidWritesRule, + 'no-unsafe-refs': rules.UnsafeRefsRule, + 'validate-set-state-in-render': rules.ValidateSetStateInRenderRule, + 'no-set-state-in-effects': rules.NoSetStateInEffectsRule, + 'no-ref-access-in-render': rules.NoRefAccessInRenderRule, + 'no-impure-function-calls': rules.NoImpureFunctionCallsRule, + 'unnecessary-effects': rules.UnnecessaryEffectsRule, + 'no-ambiguous-jsx': rules.NoAmbiguousJsxRule, +}; + +const recommendedRules = { + ...validationRules, + 'no-unsupported-syntax': rules.NoUnsupportedSyntaxRule, + + /** Validation for React Compiler inline configuration */ + 'no-unused-directives': rules.NoUnusedDirectivesRule, + 'validate-compiler-config': rules.ValidateCompilerConfigRule, +}; + +const allRules = { + ...recommendedRules, + /** Warn on syntax that React Compiler cannot transform */ + 'no-todo-syntax': rules.WarnOnTodoSyntaxRule, + 'warn-on-compilation-failures': rules.WarnOnUnactionableFailuresRule, }; const configs = { recommended: { plugins: { 'react-compiler': { - rules: { - 'react-compiler': ReactCompilerRule, - }, + rules: recommendedRules, }, }, - rules: { - 'react-compiler/react-compiler': 'error' as const, - }, + rules: Object.fromEntries( + Object.keys(recommendedRules).map(ruleName => [ + 'react-compiler/' + ruleName, + 'error', + ]), + ) as Record, }, }; -export {configs, rules, meta}; +export {configs, allRules as rules, meta}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index 730b6ff6f8..0058464090 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -5,43 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import {transformFromAstSync} from '@babel/core'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import type {SourceLocation as BabelSourceLocation} from '@babel/types'; -import BabelPluginReactCompiler, { - CompilerDiagnostic, +import { CompilerDiagnosticOptions, - CompilerErrorDetail, CompilerErrorDetailOptions, CompilerSuggestionOperation, - ErrorSeverity, - parsePluginOptions, - validateEnvironmentConfig, - OPT_OUT_DIRECTIVES, - type PluginOptions, } from 'babel-plugin-react-compiler/src'; -import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Rule} from 'eslint'; -import {Statement} from 'estree'; -import * as HermesParser from 'hermes-parser'; +import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler'; +import {LinterCategory} from 'babel-plugin-react-compiler/src/CompilerError'; function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } -const DEFAULT_REPORTABLE_LEVELS = new Set([ - ErrorSeverity.InvalidReact, - ErrorSeverity.InvalidJS, -]); -let reportableLevels = DEFAULT_REPORTABLE_LEVELS; - -function isReportableDiagnostic( - detail: CompilerErrorDetail | CompilerDiagnostic, -): boolean { - return reportableLevels.has(detail.severity); -} - function makeSuggestions( detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array { @@ -95,28 +72,97 @@ function makeSuggestions( return suggest; } -const COMPILER_OPTIONS: Partial = { - noEmit: true, - panicThreshold: 'none', - // Don't emit errors on Flow suppressions--Flow already gave a signal - flowSuppressions: false, - environment: validateEnvironmentConfig({ - validateRefAccessDuringRender: true, - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - }), -}; +function getReactCompilerResult(context: Rule.RuleContext): RunCacheEntry { + // Compat with older versions of eslint + const sourceCode = context.sourceCode ?? context.getSourceCode(); + const filename = context.filename ?? context.getFilename(); + const userOpts = context.options[0] ?? {}; -const rule: Rule.RuleModule = { + const results = runReactCompiler({ + sourceCode, + filename, + userOpts, + }); + + return results; +} + +function hasFlowSuppression( + program: RunCacheEntry, + nodeLoc: BabelSourceLocation, + suppressions: Array, +): boolean { + for (const commentNode of program.flowSuppressions) { + if ( + suppressions.includes(commentNode.code) && + commentNode.line === nodeLoc.start.line - 1 + ) { + return true; + } + } + return false; +} + +function makeRule(filter: Array): Rule.RuleModule['create'] { + return (context: Rule.RuleContext): Rule.RuleListener => { + const result = getReactCompilerResult(context); + + for (const event of result.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if ( + detail.linterCategory != null && + filter.includes(detail.linterCategory) + ) { + const loc = detail.primaryLocation(); + if (loc == null || typeof loc === 'symbol') { + continue; + } + if ( + hasFlowSuppression(result, loc, [ + 'react-rule-hook', + 'react-rule-unsafe-ref', + ]) + ) { + // If Flow already caught this error, we don't need to report it again. + continue; + } + // TODO: if multiple rules report the same linter category, + // we should deduplicate them with a "reported" set + context.report({ + message: detail.printErrorMessage(result.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); + } + } + } + return {}; + }; +} + +const unactionableBailouts: Rule.RuleModule = { meta: { type: 'problem', docs: { - description: 'Surfaces diagnostics from React Forget', + description: 'Surfaces unactionable compilation bailouts', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + }, + create(context: Rule.RuleContext): Rule.RuleListener { + return {}; + }, +}; + +export const RulesOfHooksRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to the rules of hooks', recommended: true, }, fixable: 'code', @@ -124,234 +170,284 @@ const rule: Rule.RuleModule = { // validation is done at runtime with zod schema: [{type: 'object', additionalProperties: true}], }, - create(context: Rule.RuleContext) { - // Compat with older versions of eslint - const sourceCode = context.sourceCode ?? context.getSourceCode(); - const filename = context.filename ?? context.getFilename(); - const userOpts = context.options[0] ?? {}; - if ( - userOpts.reportableLevels != null && - userOpts.reportableLevels instanceof Set - ) { - reportableLevels = userOpts.reportableLevels; - } else { - reportableLevels = DEFAULT_REPORTABLE_LEVELS; - } - /** - * Experimental setting to report all compilation bailouts on the compilation - * unit (e.g. function or hook) instead of the offensive line. - * Intended to be used when a codebase is 100% reliant on the compiler for - * memoization (i.e. deleted all manual memo) and needs compilation success - * signals for perf debugging. - */ - let __unstable_donotuse_reportAllBailouts: boolean = false; - if ( - userOpts.__unstable_donotuse_reportAllBailouts != null && - typeof userOpts.__unstable_donotuse_reportAllBailouts === 'boolean' - ) { - __unstable_donotuse_reportAllBailouts = - userOpts.__unstable_donotuse_reportAllBailouts; - } + create: makeRule([LinterCategory.RULES_OF_HOOKS]), +}; - let shouldReportUnusedOptOutDirective = true; - const options: PluginOptions = parsePluginOptions({ - ...COMPILER_OPTIONS, - ...userOpts, - environment: { - ...COMPILER_OPTIONS.environment, - ...userOpts.environment, - }, - }); - const userLogger: Logger | null = options.logger; - options.logger = { - logEvent: (eventFilename, event): void => { - userLogger?.logEvent(eventFilename, event); - if (event.kind === 'CompileError') { - shouldReportUnusedOptOutDirective = false; - const detail = event.detail; - const suggest = makeSuggestions(detail.options); - if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) { - const loc = detail.primaryLocation(); - const locStr = - loc != null && typeof loc !== 'symbol' - ? ` (@:${loc.start.line}:${loc.start.column})` - : ''; - /** - * Report bailouts with a smaller span (just the first line). - * Compiler bailout lints only serve to flag that a react function - * has not been optimized by the compiler for codebases which depend - * on compiler memo heavily for perf. These lints are also often not - * actionable. - */ - let endLoc; - if (event.fnLoc.end.line === event.fnLoc.start.line) { - endLoc = event.fnLoc.end; - } else { - endLoc = { - line: event.fnLoc.start.line, - // Babel loc line numbers are 1-indexed - column: - sourceCode.text.split(/\r?\n|\r|\n/g)[ - event.fnLoc.start.line - 1 - ]?.length ?? 0, - }; - } - const firstLineLoc = { - start: event.fnLoc.start, - end: endLoc, - }; - context.report({ - message: `${detail.printErrorMessage(sourceCode.text, {eslint: true})} ${locStr}`, - loc: firstLineLoc, - suggest, - }); - } +export const NoCapitalizedCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to capitalized calls', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.CAPITALIZED_CALLS]), +}; +export const StaticComponentsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'todo', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.STATIC_COMPONENTS]), +}; + +export const InvalidWritesRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.INVALID_WRITE]), +}; +export const UseMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Surfaces compilation errors related to invalid useMemo usage', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.VALIDATE_MANUAL_MEMO]), +}; + +// TODO: test cases +export const NoDynamicManualMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.DYNAMIC_MANUAL_MEMO]), +}; + +export const UnsafeRefsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to unsafe refs', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.EXHAUSTIVE_DEPS]), +}; + +export const ValidateSetStateInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_RENDER]), +}; + +export const NoSetStateInEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to setState in render', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_EFFECTS]), +}; + +export const NoRefAccessInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_REF_ACCESS_IN_RENDER]), +}; + +export const NoImpureFunctionCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.IMPURE_FUNCTIONS]), +}; + +export const UnnecessaryEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNNECESSARY_EFFECTS]), +}; + +export const NoAmbiguousJsxRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on JSX usage that is ambiguous.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.JSX_IN_TRY]), +}; + +// TODO: test cases +export const NoUnsupportedSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler does not and will not support.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNSUPPORTED_SYNTAX]), +}; + +// TODO: test cases +export const WarnOnTodoSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler currently does not support, but may in the future.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.TODO_SYNTAX]), +}; + +// TODO: test cases +export const ValidateCompilerConfigRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Validates the React Compiler configuration', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.COMPILER_CONFIG]), +}; + +export const WarnOnUnactionableFailuresRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on compilation failures that are not actionable', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const event of results.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if (detail.linterCategory == null) { const loc = detail.primaryLocation(); - if ( - !isReportableDiagnostic(detail) || - loc == null || - typeof loc === 'symbol' - ) { - return; + if (loc == null || typeof loc === 'symbol') { + continue; } - if ( - hasFlowSuppression(loc, 'react-rule-hook') || - hasFlowSuppression(loc, 'react-rule-unsafe-ref') - ) { - // If Flow already caught this error, we don't need to report it again. - return; - } - if (loc != null) { - context.report({ - message: detail.printErrorMessage(sourceCode.text, { - eslint: true, - }), - loc, - suggest, - }); - } - } - }, - }; - - try { - options.environment = validateEnvironmentConfig( - options.environment ?? {}, - ); - } catch (err: unknown) { - options.logger?.logEvent('', err as LoggerEvent); - } - - function hasFlowSuppression( - nodeLoc: BabelSourceLocation, - suppression: string, - ): boolean { - const comments = sourceCode.getAllComments(); - const flowSuppressionRegex = new RegExp( - '\\$FlowFixMe\\[' + suppression + '\\]', - ); - for (const commentNode of comments) { - if ( - flowSuppressionRegex.test(commentNode.value) && - commentNode.loc!.end.line === nodeLoc.start.line - 1 - ) { - return true; + context.report({ + message: detail.printErrorMessage(results.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); } } - return false; - } - - let babelAST; - if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { - try { - const {parse: babelParse} = require('@babel/parser'); - babelAST = babelParse(sourceCode.text, { - filename, - sourceType: 'unambiguous', - plugins: ['typescript', 'jsx'], - }); - } catch { - /* empty */ - } - } else { - try { - babelAST = HermesParser.parse(sourceCode.text, { - babel: true, - enableExperimentalComponentSyntax: true, - sourceFilename: filename, - sourceType: 'module', - }); - } catch { - /* empty */ - } - } - - if (babelAST != null) { - try { - transformFromAstSync(babelAST, sourceCode.text, { - filename, - highlightCode: false, - retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], - sourceType: 'module', - configFile: false, - babelrc: false, - }); - } catch (err) { - /* errors handled by injected logger */ - } - } - - function reportUnusedOptOutDirective(stmt: Statement) { - if ( - stmt.type === 'ExpressionStatement' && - stmt.expression.type === 'Literal' && - typeof stmt.expression.value === 'string' && - OPT_OUT_DIRECTIVES.has(stmt.expression.value) && - stmt.loc != null - ) { - context.report({ - message: `Unused '${stmt.expression.value}' directive`, - loc: stmt.loc, - suggest: [ - { - desc: 'Remove the directive', - fix(fixer) { - return fixer.remove(stmt); - }, - }, - ], - }); - } - } - if (shouldReportUnusedOptOutDirective) { - return { - FunctionDeclaration(fnDecl) { - for (const stmt of fnDecl.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - ArrowFunctionExpression(fnExpr) { - if (fnExpr.body.type === 'BlockStatement') { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - } - }, - FunctionExpression(fnExpr) { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - }; - } else { - return {}; } + return {}; }, }; -export default rule; +export const NoUnusedDirectivesRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const directive of results.unusedOptOutDirectives) { + context.report({ + message: `Unused '${directive.directive}' directive`, + loc: directive.loc, + suggest: [ + { + desc: 'Remove the directive', + fix(fixer) { + return fixer.removeRange(directive.range); + }, + }, + ], + }); + } + return {}; + }, +}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts new file mode 100644 index 0000000000..655eaf5197 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -0,0 +1,323 @@ +/** + * 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 {transformFromAstSync, traverse} from '@babel/core'; +import {parse as babelParse} from '@babel/parser'; +import {Directive, File} from '@babel/types'; +// @ts-expect-error: no types available +import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; +import BabelPluginReactCompiler, { + parsePluginOptions, + validateEnvironmentConfig, + OPT_OUT_DIRECTIVES, + type PluginOptions, +} from 'babel-plugin-react-compiler/src'; +import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; +import type {SourceCode} from 'eslint'; +import {SourceLocation} from 'estree'; +// @ts-expect-error: no types available +import * as HermesParser from 'hermes-parser'; +import {isDeepStrictEqual} from 'util'; +import type {ParseResult} from '@babel/parser'; + +const COMPILER_OPTIONS: Partial = { + noEmit: true, + panicThreshold: 'none', + // Don't emit errors on Flow suppressions--Flow already gave a signal + flowSuppressions: false, + environment: validateEnvironmentConfig({ + validateRefAccessDuringRender: true, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + // TODO: remove, this should be in the type system + validateNoCapitalizedCalls: [], + validateHooksUsage: true, + validateNoDerivedComputationsInEffects: true, + }), +}; + +export type UnusedOptOutDirective = { + loc: SourceLocation; + range: [number, number]; + directive: string; +}; +export type RunCacheEntry = { + sourceCode: string; + filename: string; + userOpts: PluginOptions; + flowSuppressions: Array<{line: number; code: string}>; + unusedOptOutDirectives: Array; + events: LoggerEvent[]; +}; + +type RunParams = { + sourceCode: SourceCode; + filename: string; + userOpts: PluginOptions; +}; +const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g; + +function getFlowSuppressions( + sourceCode: SourceCode, +): Array<{line: number; code: string}> { + const comments = sourceCode.getAllComments(); + const results: Array<{line: number; code: string}> = []; + + for (const commentNode of comments) { + const matches = commentNode.value.matchAll(FLOW_SUPPRESSION_REGEX); + for (const match of matches) { + if (match.index != null && commentNode.loc != null) { + const code = match[1]; + results.push({ + line: commentNode.loc!.end.line, + code, + }); + } + } + } + return results; +} + +function filterUnusedOptOutDirectives( + directives: ReadonlyArray, +): Array { + const results: Array = []; + for (const directive of directives) { + if ( + OPT_OUT_DIRECTIVES.has(directive.value.value) && + directive.loc != null + ) { + results.push({ + loc: directive.loc, + directive: directive.value.value, + range: [directive.start!, directive.end!], + }); + } + } + return results; +} + +function runReactCompilerImpl({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + // Compat with older versions of eslint + for (const [key, entry] of Object.entries(userOpts)) { + if (key === 'environment' && COMPILER_OPTIONS.environment != null) { + for (const envKey of Object.keys(entry as Record)) { + if ( + COMPILER_OPTIONS.environment.hasOwnProperty(envKey) && + isDeepStrictEqual( + (entry as Record)[envKey], + (COMPILER_OPTIONS.environment as Record)[envKey], + ) + ) { + console.warn('Conflicting environment option detected: ' + envKey); + } + } + } else if (COMPILER_OPTIONS.hasOwnProperty(key)) { + if (isDeepStrictEqual(entry, (COMPILER_OPTIONS as any)[key])) { + console.warn('Conflicting option detected: ' + key); + } + } + } + const options: PluginOptions = parsePluginOptions({ + ...COMPILER_OPTIONS, + ...userOpts, + environment: { + ...COMPILER_OPTIONS.environment, + ...userOpts.environment, + }, + }); + const results: RunCacheEntry = { + sourceCode: sourceCode.text, + filename, + userOpts, + flowSuppressions: [], + unusedOptOutDirectives: [], + events: [], + }; + const userLogger: Logger | null = options.logger; + options.logger = { + logEvent: (eventFilename, event): void => { + userLogger?.logEvent(eventFilename, event); + results.events.push(event); + }, + }; + + try { + options.environment = validateEnvironmentConfig(options.environment ?? {}); + } catch (err: unknown) { + options.logger?.logEvent(filename, err as LoggerEvent); + } + + let babelAST: ParseResult | null = null; + if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { + try { + babelAST = babelParse(sourceCode.text, { + sourceFilename: filename, + sourceType: 'unambiguous', + plugins: ['typescript', 'jsx'], + }); + } catch { + /* empty */ + } + } else { + try { + babelAST = HermesParser.parse(sourceCode.text, { + babel: true, + enableExperimentalComponentSyntax: true, + sourceFilename: filename, + sourceType: 'module', + }); + } catch { + /* empty */ + } + } + + if (babelAST != null) { + results.flowSuppressions = getFlowSuppressions(sourceCode); + try { + transformFromAstSync(babelAST, sourceCode.text, { + filename, + highlightCode: false, + retainLines: true, + plugins: [ + [PluginProposalPrivateMethods, {loose: true}], + [BabelPluginReactCompiler, options], + ], + sourceType: 'module', + configFile: false, + babelrc: false, + }); + + if (results.events.filter(e => e.kind === 'CompileError').length === 0) { + traverse(babelAST, { + FunctionDeclaration(path) { + path.node; + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + ArrowFunctionExpression(path) { + if (path.node.body.type === 'BlockStatement') { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + } + }, + FunctionExpression(path) { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + }); + } + } catch (err) { + /* errors handled by injected logger */ + } + } + + return results; +} + +const SENTINEL = Symbol(); + +type T = {[k: string]: any}; + +// Array backed LRU cache -- should be small < 10 elements +class LRUCache { + // newest at headIdx, then headIdx + 1, ..., tailIdx + #values: Array<[K, T | Error] | [typeof SENTINEL, void]>; + #headIdx: number = 0; + // #store: (key: K) => T | Error; + // #isValue: null | ((key: T | Error) => key is T); + + constructor( + size: number, + // store: (key: K) => T | Error, + // isValue?: (key: T | Error) => key is T, + ) { + this.#values = new Array(size).fill(SENTINEL); + // this.#store = store; + // this.#isValue = isValue ?? null; + } + + // gets a value and sets it as "recently used" + get(key: K): T | null { + let idx = this.#values.findIndex(entry => entry[0] === key); + // If found, move to front + if (idx === this.#headIdx) { + return this.#values[this.#headIdx][1] as T; + } else if (idx < 0) { + return null; + // const value = this.#store(key); + // if (this.#isValue && !this.#isValue(value)) { + // return value; // Return error directly + // } + // this.#headIdx = + // (this.#headIdx - 1 + this.#values.length) % this.#values.length; + // this.#values[this.#headIdx] = [key, value]; + } + + const entry: [K, T] = this.#values[idx] as [K, T]; + + const len = this.#values.length; + for (let i = 0; i < Math.min(idx, len - 1); i++) { + this.#values[(this.#headIdx + i + 1) % len] = + this.#values[(this.#headIdx + i) % len]; + } + this.#values[this.#headIdx] = entry; + return entry[1]; + } + push(key: K, value: T): void { + this.#headIdx = + (this.#headIdx - 1 + this.#values.length) % this.#values.length; + this.#values[this.#headIdx] = [key, value]; + } +} +const cache = new LRUCache(10); + +export default function runReactCompiler({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + const entry = cache.get(filename); + if ( + entry != null && + entry.sourceCode === sourceCode.text && + isDeepStrictEqual(entry.userOpts, userOpts) + ) { + return entry; + } else if (entry != null) { + if (process.env['DEBUG']) { + console.log( + `Cache hit for ${filename}, but source code or options changed, recomputing`, + ); + } + } + + const runEntry = runReactCompilerImpl({ + sourceCode, + filename, + userOpts, + }); + // If we have a cache entry, we can update it + if (entry != null) { + Object.assign(entry, runEntry); + } else { + cache.push(filename, runEntry); + } + return {...runEntry}; +} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index a159359773..a6041bd5cc 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -338,7 +338,16 @@ export async function transformFixtureInput( if (logs.length !== 0) { formattedLogs = logs .map(({event}) => { - return JSON.stringify(event); + return JSON.stringify(event, (key, value) => { + if ( + key === 'detail' && + value != null && + typeof value.serialize === 'function' + ) { + return value.serialize(); + } + return value; + }); }) .join('\n'); } diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 6e1bc7feeb..696261cbf5 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -542,11 +542,6 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - "@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" @@ -1605,7 +1600,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": +"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.27.0", "@babel/types@^7.27.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== @@ -1613,14 +1608,6 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -2148,6 +2135,11 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/diff-sequences@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + "@jest/environment@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz" @@ -2178,6 +2170,13 @@ "@types/node" "*" jest-mock "^29.5.0" +"@jest/expect-utils@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz#9d42e4b8bc80367db30abc6c42b2cb14073f66fc" + integrity sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew== + dependencies: + "@jest/get-type" "30.0.1" + "@jest/expect-utils@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz" @@ -2274,6 +2273,11 @@ jest-mock "^29.5.0" jest-util "^29.5.0" +"@jest/get-type@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.0.1.tgz#0d32f1bbfba511948ad247ab01b9007724fc9f52" + integrity sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw== + "@jest/globals@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz" @@ -2313,6 +2317,14 @@ "@jest/types" "^29.6.3" jest-mock "^29.7.0" +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + "@jest/reporters@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz" @@ -2435,6 +2447,13 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/schemas@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" @@ -2663,6 +2682,19 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz" @@ -2965,6 +2997,11 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sinclair/typebox@^0.34.0": + version "0.34.38" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.38.tgz#2365df7c23406a4d79413a766567bfbca708b49d" + integrity sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" @@ -3154,6 +3191,11 @@ resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== +"@types/istanbul-lib-coverage@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + "@types/istanbul-lib-report@*": version "3.0.0" resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" @@ -3176,6 +3218,13 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jest@^28.1.6": version "28.1.8" resolved "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz" @@ -3200,6 +3249,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + "@types/jsdom@^20.0.0": version "20.0.0" resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz" @@ -3282,6 +3339,11 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz" @@ -3309,6 +3371,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.33": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz" @@ -3740,7 +3809,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: +ansi-styles@^5.0.0, ansi-styles@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== @@ -3803,11 +3872,32 @@ aria-query@^5.0.0: resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.0.2.tgz" integrity sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q== +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + ast-types@^0.13.4: version "0.13.4" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz" @@ -3815,6 +3905,11 @@ ast-types@^0.13.4: dependencies: tslib "^2.0.1" +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + async@^3.2.3: version "3.2.6" resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" @@ -3825,6 +3920,13 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + axios@^1.6.1: version "1.7.4" resolved "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz" @@ -4245,7 +4347,7 @@ cac@^6.7.14: resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== @@ -4253,7 +4355,17 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -call-bound@^1.0.2: +call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== @@ -4295,7 +4407,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: +chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4377,6 +4489,11 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz" integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== +ci-info@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" @@ -4722,6 +4839,33 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + date-fns@^2.29.1: version "2.30.0" resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz" @@ -4788,6 +4932,24 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + degenerator@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz" @@ -4922,7 +5084,7 @@ dreamopt@~0.6.0: dependencies: wordwrap ">=0.0.2" -dunder-proto@^1.0.1: +dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== @@ -5033,7 +5195,67 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.1: +es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.9: + version "1.24.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" + integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== @@ -5050,6 +5272,25 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" + es5-ext@0.8.x: version "0.8.2" resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz" @@ -5428,6 +5669,18 @@ expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expect@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.0.5.tgz#c23bf193c5e422a742bfd2990ad990811de41a5a" + integrity sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ== + dependencies: + "@jest/expect-utils" "30.0.5" + "@jest/get-type" "30.0.1" + jest-matcher-utils "30.0.5" + jest-message-util "30.0.5" + jest-mock "30.0.5" + jest-util "30.0.5" + express-rate-limit@^7.5.0: version "7.5.0" resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz" @@ -5719,6 +5972,13 @@ follow-redirects@^1.15.6: resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz" @@ -5788,6 +6048,23 @@ function-bind@^1.1.2: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -5798,7 +6075,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -5819,7 +6096,7 @@ get-package-type@^0.1.0: resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proto@^1.0.1: +get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -5839,6 +6116,15 @@ get-stream@^6.0.0: resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + get-uri@^6.0.1: version "6.0.4" resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz" @@ -5957,6 +6243,14 @@ globals@^14.0.0: resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" @@ -5969,12 +6263,12 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.2.0: +gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -5989,6 +6283,11 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" @@ -5999,11 +6298,32 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.1.0: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" @@ -6231,6 +6551,15 @@ ini@^1.3.4: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" @@ -6251,6 +6580,15 @@ ipaddr.js@1.9.1: resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -6261,6 +6599,24 @@ is-arrayish@^0.3.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" @@ -6268,6 +6624,19 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-core-module@^2.9.0: version "2.10.0" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" @@ -6275,11 +6644,35 @@ is-core-module@^2.9.0: dependencies: has "^1.0.3" +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" @@ -6290,6 +6683,16 @@ is-generator-fn@^2.0.0: resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== + dependencies: + call-bound "^1.0.3" + get-proto "^1.0.0" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -6307,6 +6710,24 @@ is-interactive@^2.0.0: resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" @@ -6339,11 +6760,57 @@ is-promise@^4.0.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz" integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" @@ -6354,11 +6821,36 @@ is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0: resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2, is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-windows@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -6797,6 +7289,16 @@ jest-config@^29.7.0: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-diff@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.5.tgz#b40f81e0c0d13e5b81c4d62b0d0dfa6a524ee0fd" + integrity sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + pretty-format "30.0.5" + jest-diff@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz" @@ -7106,6 +7608,16 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-matcher-utils@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz#dff3334be58faea4a5e1becc228656fbbfc2467d" + integrity sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ== + dependencies: + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + jest-diff "30.0.5" + pretty-format "30.0.5" + jest-matcher-utils@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz" @@ -7146,6 +7658,21 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-message-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.5.tgz#dd12ffec91dd3fa6a59cbd538a513d8e239e070c" + integrity sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-message-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz" @@ -7206,6 +7733,15 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + jest-util "30.0.5" + jest-mock@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz" @@ -7237,6 +7773,11 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + jest-regex-util@^28.0.2: version "28.0.2" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" @@ -7683,6 +8224,18 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + jest-util@^28.0.0, jest-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" @@ -8327,7 +8880,7 @@ merge@^2.1.1: resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -8620,11 +9173,28 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.3: +object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + on-finished@^2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" @@ -8695,6 +9265,15 @@ ora@^7.0.1: string-width "^6.1.0" strip-ansi "^7.1.0" +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -8949,6 +9528,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + postcss-load-config@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz" @@ -8975,6 +9559,15 @@ prettier@^3.3.3: resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +pretty-format@30.0.5, pretty-format@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + pretty-format@^24: version "24.9.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz" @@ -9202,7 +9795,7 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -9251,6 +9844,20 @@ readline@^1.3.0: resolved "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz" integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz" @@ -9280,6 +9887,30 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" +regexp.escape@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexp.escape/-/regexp.escape-2.0.1.tgz#09e4beef9d202dbd739868f3818223f977cf91da" + integrity sha512-JItRb4rmyTzmERBkAf6J87LjDPy/RscIwmaJQ3gsFlAzrmZbZU8LwBw5IydFZXW9hqpgbPlGbMhtpqtuAhMgtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + for-each "^0.3.3" + safe-regex-test "^1.0.3" + +regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + regexpu-core@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz" @@ -9457,6 +10088,17 @@ rxjs@^7.0.0, rxjs@^7.8.1: dependencies: tslib "^2.1.0" +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" + isarray "^2.0.5" + safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" @@ -9467,6 +10109,23 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + safe-stable-stringify@^2.3.1: version "2.5.0" resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" @@ -9576,6 +10235,37 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" @@ -9759,6 +10449,13 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" @@ -9771,6 +10468,14 @@ stdin-discarder@^0.1.0: dependencies: bl "^5.0.0" +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + streamx@^2.15.0, streamx@^2.21.0: version "2.22.0" resolved "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz" @@ -9825,6 +10530,38 @@ string-width@^6.1.0: emoji-regex "^10.2.1" strip-ansi "^7.0.1" +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + +string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -10232,6 +10969,51 @@ type-is@^2.0.0, type-is@^2.0.1: media-typer "^1.1.0" mime-types "^3.0.0" +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + typed-query-selector@^2.12.0: version "2.12.0" resolved "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz" @@ -10251,6 +11033,16 @@ typescript@^5.4.3: resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" @@ -10460,11 +11252,64 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which@^1.2.10, which@^1.2.14: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" From d14b47ec8f157ba04619339b5687c5f118e6ad96 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 8 Aug 2025 12:18:50 -0400 Subject: [PATCH 419/422] [compiler] Aggregate error reporting, separate eslint rules All error messages that are not invariants, todos, or config validation errors are now moved to a registry. This helps us check that error messages and linked documentation is uniform and up to date. This also lets us link error codes to linter reporting categories and break the single `react-compiler` rule up into many smaller rules. Changes to error reporting: - instead of writing raw error message into your code, add a new ErrorCode to `CompilerErrorCodes` - create errors by constructing a `CompilerErrorDetailOptions` with the correct error code, or calling `CompilerErrorDetail.fromCode()` / `CompilerDiagnostic.fromCode()`. Changes to linter: - `react-compiler` lint rule is now broken up into many smaller lint rules, each with their own test cases. - linting no longer fails early when a non-fatal error is find. Instead, we now log and move on to lint for more errors! Todos: - Codebases previously using the eslint plugin may already have `react-compiler` eslint suppressions. Since this PR removes this rule, these codebases may get new eslint warnings on already-suppressed ranges. Some options I can think of: - Force everyone to add the new suppressions - Release a script to rewrite previous suppressions to the new suppressions. Codebases that had incomplete suppressions / waited to update may benefit here, as this still lets us error on previously-unseen suppressions - Hard code logic in our eslint plugin to avoid reporting if we see a `eslint-disable-next-line react-compiler`. This doesn't feel ideal - Align on eslint plugin rule names and error code mappings - I aggregated a few error codes here. For example, reassigning and modifying local variables after render now have the same error code + message. See changes fixtures for more examples --- .../src/CompilerError.ts | 267 ++++-- .../src/Entrypoint/Imports.ts | 4 +- .../src/Entrypoint/Options.ts | 9 +- .../src/Entrypoint/Pipeline.ts | 36 +- .../src/Entrypoint/Program.ts | 47 +- .../src/Entrypoint/Suppression.ts | 14 +- .../ValidateNoUntransformedReferences.ts | 33 +- .../src/Flood/TypeErrors.ts | 41 +- .../src/HIR/BuildHIR.ts | 194 ++-- .../src/HIR/Environment.ts | 10 +- .../src/HIR/HIR.ts | 15 +- .../src/HIR/HIRBuilder.ts | 27 +- .../src/Inference/DropManualMemoization.ts | 77 +- .../src/Inference/InferFunctionEffects.ts | 36 +- .../Inference/InferMutationAliasingEffects.ts | 66 +- .../src/Inference/InferReferenceEffects.ts | 16 +- .../src/Transform/TransformFire.ts | 27 +- .../src/Utils/CompilerErrorCodes.ts | 611 ++++++++++++ .../src/Utils/CompilerErrorSeverity.ts | 34 + .../src/Validation/ValidateHooksUsage.ts | 58 +- .../ValidateLocalsNotReassignedAfterRender.ts | 19 +- .../ValidateMemoizedEffectDependencies.ts | 9 +- .../Validation/ValidateNoCapitalizedCalls.ts | 14 +- .../ValidateNoDerivedComputationsInEffects.ts | 21 +- ...ValidateNoFreezingKnownMutableFunctions.ts | 7 +- .../ValidateNoImpureFunctionsInRender.ts | 19 +- .../Validation/ValidateNoJSXInTryStatement.ts | 9 +- .../Validation/ValidateNoRefAccessInRender.ts | 101 +- .../Validation/ValidateNoSetStateInEffects.ts | 23 +- .../Validation/ValidateNoSetStateInRender.ts | 31 +- .../ValidatePreservedManualMemoization.ts | 46 +- .../Validation/ValidateStaticComponents.ts | 11 +- .../src/Validation/ValidateUseMemo.ts | 28 +- ...global-in-component-tag-function.expect.md | 4 +- ...or.assign-global-in-jsx-children.expect.md | 4 +- ...n-global-in-jsx-spread-attribute.expect.md | 6 +- ...rror.bailout-on-flow-suppression.expect.md | 2 +- ...ut-on-suppression-of-custom-rule.expect.md | 4 +- ...ext-variable-only-chained-assign.expect.md | 2 +- ...variable-in-function-declaration.expect.md | 2 +- ...erences-variable-its-assigned-to.expect.md | 2 +- ...destructure-assignment-to-global.expect.md | 4 +- ...ucture-to-local-global-variables.expect.md | 4 +- ...lid-global-reassignment-indirect.expect.md | 4 +- .../error.invalid-hoisting-setstate.expect.md | 4 +- ...valid-impure-functions-in-render.expect.md | 18 +- ...n-of-possible-props-phi-indirect.expect.md | 2 +- ...eassign-local-variable-in-effect.expect.md | 2 +- .../error.invalid-reassign-const.expect.md | 4 +- ...ssign-local-in-hook-return-value.expect.md | 2 +- ...eassign-local-variable-in-effect.expect.md | 2 +- ...-local-variable-in-hook-argument.expect.md | 2 +- ...n-local-variable-in-jsx-callback.expect.md | 2 +- ....invalid-sketchy-code-use-forget.expect.md | 4 +- ...alid-unclosed-eslint-suppression.expect.md | 2 +- ...nconditional-set-state-in-render.expect.md | 4 +- ...ange-shared-inner-outer-function.expect.md | 2 +- ...rror.mutate-property-from-global.expect.md | 2 +- ...or.not-useEffect-external-mutate.expect.md | 4 +- ...r.object-capture-global-mutation.expect.md | 6 +- .../error.reassign-global-fn-arg.expect.md | 4 +- ....reassignment-to-global-indirect.expect.md | 8 +- .../error.reassignment-to-global.expect.md | 8 +- ...ror.sketchy-code-exhaustive-deps.expect.md | 2 +- ...rror.sketchy-code-rules-of-hooks.expect.md | 2 +- .../error.store-property-in-global.expect.md | 2 +- ...ences-later-variable-declaration.expect.md | 2 +- ...state-in-render-after-loop-break.expect.md | 2 +- ...l-set-state-in-render-after-loop.expect.md | 2 +- ...-state-in-render-with-loop-throw.expect.md | 2 +- ...r.unconditional-set-state-lambda.expect.md | 2 +- ...tate-nested-function-expressions.expect.md | 2 +- ...ror.update-global-should-bailout.expect.md | 4 +- ...ror.useMemo-non-literal-depslist.expect.md | 4 +- .../dynamic-gating-invalid-multiple.expect.md | 2 +- .../no-emit/retry-no-emit.expect.md | 5 +- ...setState-in-useEffect-transitive.expect.md | 2 +- .../invalid-setState-in-useEffect.expect.md | 2 +- ...valid-impure-functions-in-render.expect.md | 18 +- ...n-local-variable-in-jsx-callback.expect.md | 2 +- ...rozen-hoisted-storecontext-const.expect.md | 4 +- ...or.not-useEffect-external-mutate.expect.md | 4 +- ....reassignment-to-global-indirect.expect.md | 8 +- .../error.reassignment-to-global.expect.md | 8 +- .../new-mutability/retry-no-emit.expect.md | 5 +- ....validate-useMemo-named-function.expect.md | 2 - .../bailout-retry/error.todo-syntax.expect.md | 2 +- ...ror.untransformed-fire-reference.expect.md | 2 +- .../bailout-retry/error.use-no-memo.expect.md | 2 +- .../error.invalid-outside-effect.expect.md | 4 +- ...id-rewrite-deps-no-array-literal.expect.md | 2 +- ...rror.invalid-rewrite-deps-spread.expect.md | 2 +- .../__tests__/ImpureFunctionCallsRule-test.ts | 32 + .../__tests__/InvalidHooksRule-test.ts | 85 ++ .../__tests__/NoAmbiguousJsxRule-test.ts | 31 + .../__tests__/NoCapitalizedCallsRule-test.ts | 58 ++ .../__tests__/NoRefAccessInRender-tests.ts | 27 + .../__tests__/NoSetStateInEffects-tests.ts | 48 + .../__tests__/NoSetStateInRender-test.ts | 58 ++ .../__tests__/NoUnusedDirectivesRule-test.ts | 58 ++ .../__tests__/PluginTest-test.ts | 172 ++++ .../__tests__/ReactCompilerRule-test.ts | 287 ------ .../ReactCompilerRuleTypescript-test.ts | 24 +- .../__tests__/StaticComponentsRule-test.ts | 44 + .../__tests__/UnnecessaryEffects-tests.ts | 36 + .../__tests__/ValidateUseMemo-test.ts | 43 + .../__tests__/shared-utils.ts | 98 ++ .../eslint-plugin-react-compiler/package.json | 4 +- .../eslint-plugin-react-compiler/src/index.ts | 52 +- .../src/rules/ReactCompilerRule.ts | 626 ++++++------ .../src/shared/RunReactCompiler.ts | 323 +++++++ compiler/packages/snap/src/compiler.ts | 11 +- compiler/yarn.lock | 901 +++++++++++++++++- 113 files changed, 3763 insertions(+), 1437 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts delete mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index ee0d0f74c5..6bce5dca2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -10,48 +10,22 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {SourceLocation} from './HIR'; import {Err, Ok, Result} from './Utils/Result'; import {assertExhaustive} from './Utils/utils'; - -export enum ErrorSeverity { - /** - * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some - * misunderstanding on the user’s part. - */ - InvalidJS = 'InvalidJS', - /** - * JS syntax that is not supported and which we do not plan to support. Developers should - * rewrite to use supported forms. - */ - UnsupportedJS = 'UnsupportedJS', - /** - * Code that breaks the rules of React. - */ - InvalidReact = 'InvalidReact', - /** - * Incorrect configuration of the compiler. - */ - InvalidConfig = 'InvalidConfig', - /** - * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve - * memoization. - */ - CannotPreserveMemoization = 'CannotPreserveMemoization', - /** - * Unhandled syntax that we don't support yet. - */ - Todo = 'Todo', - /** - * An unexpected internal error in the compiler that indicates critical issues that can panic - * the compiler. - */ - Invariant = 'Invariant', -} +import {ErrorSeverity} from './Utils/CompilerErrorSeverity'; +import { + ErrorCode, + ErrorCodeDetails, + LinterCategory, +} from './Utils/CompilerErrorCodes'; +export {ErrorSeverity}; +export {ErrorCode, ErrorCodeDetails, LinterCategory}; export type CompilerDiagnosticOptions = { severity: ErrorSeverity; category: string; - description: string; + description?: string | null | undefined; details: Array; suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; }; export type CompilerDiagnosticDetail = @@ -86,13 +60,28 @@ export type CompilerSuggestion = description: string; }; -export type CompilerErrorDetailOptions = { +export type PlainCompilerErrorDetailOptions = { + errorCode?: void; reason: string; description?: string | null | undefined; - severity: ErrorSeverity; + severity: + | ErrorSeverity.Invariant + | ErrorSeverity.Todo + | ErrorSeverity.InvalidConfig; loc: SourceLocation | null; suggestions?: Array | null | undefined; }; +export type CodedCompilerErrorDetailOptions = { + errorCode: ErrorCode; + description?: string | null | undefined; + loc: SourceLocation | null; + suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; +}; + +export type CompilerErrorDetailOptions = + | PlainCompilerErrorDetailOptions + | CodedCompilerErrorDetailOptions; export type PrintErrorMessageOptions = { /** @@ -102,19 +91,72 @@ export type PrintErrorMessageOptions = { eslint: boolean; }; +export function makeCompilerDiagnostic( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + }, +): CompilerDiagnostic { + return makeCompilerDiagnostic(code, options); +} + export class CompilerDiagnostic { options: CompilerDiagnosticOptions; - constructor(options: CompilerDiagnosticOptions) { + /** + * Constructor is private to enforce that we either only create invariant diagnostics + * or use ErrorCodes + */ + private constructor(options: CompilerDiagnosticOptions) { this.options = options; } - static create( - options: Omit, - ): CompilerDiagnostic { + static create< + T extends CompilerDiagnosticOptions & {severity: ErrorSeverity.Invariant}, + >(options: Omit): CompilerDiagnostic { return new CompilerDiagnostic({...options, details: []}); } + static fromCode( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + details?: Array | null | undefined; + }, + ): CompilerDiagnostic { + const errorEntry = ErrorCodeDetails[code]; + let description = undefined; + if (errorEntry.description != null) { + description = errorEntry.description; + } + if (options?.description != null && options.description.length > 0) { + if (description != null && description.length > 0) { + description += ' '; + } else { + description = ''; + } + description += options.description; + } + + const diagnosticOptions: CompilerDiagnosticOptions = { + severity: errorEntry.severity, + category: errorEntry.reason, + description, + linterCategory: errorEntry.linterCategory, + suggestions: options?.suggestions, + details: options?.details ?? [], + }; + + return new CompilerDiagnostic(diagnosticOptions); + } + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return {options: {...this.options, linterCategory: undefined}}; + } + get category(): CompilerDiagnosticOptions['category'] { return this.options.category; } @@ -127,6 +169,9 @@ export class CompilerDiagnostic { get suggestions(): CompilerDiagnosticOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): CompilerDiagnosticOptions['linterCategory'] { + return this.options.linterCategory; + } withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic { this.options.details.push(detail); @@ -138,11 +183,10 @@ export class CompilerDiagnostic { } printErrorMessage(source: string, options: PrintErrorMessageOptions): string { - const buffer = [ - printErrorSummary(this.severity, this.category), - '\n\n', - this.description, - ]; + const buffer = [printErrorSummary(this.severity, this.category)]; + if (this.description != null) { + buffer.push(`\n\n${this.description}`); + } for (const detail of this.options.details) { switch (detail.kind) { case 'error': { @@ -202,13 +246,65 @@ export class CompilerErrorDetail { this.options = options; } - get reason(): CompilerErrorDetailOptions['reason'] { - return this.options.reason; + static fromCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + return new CompilerErrorDetail({ + ...details, + errorCode: code, + } as CodedCompilerErrorDetailOptions); } - get description(): CompilerErrorDetailOptions['description'] { + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return { + options: { + reason: this.reason, + description: this.description, + severity: this.severity, + loc: this.loc, + suggestions: this.suggestions, + }, + }; + } + + get reason(): string { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].reason; + } else { + return this.options.reason; + } + } + get description(): string | null | undefined { + if (this.options.errorCode != null) { + let description = undefined; + if (ErrorCodeDetails[this.options.errorCode].description != null) { + description = ErrorCodeDetails[this.options.errorCode].description; + } + if ( + this.options.description != null && + this.options.description.length > 0 + ) { + if (description != null && description.length > 0) { + description += '. '; + } else { + description = ''; + } + description += this.options.description; + } + return description; + } return this.options.description; } - get severity(): CompilerErrorDetailOptions['severity'] { + get severity(): ErrorSeverity { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].severity; + } return this.options.severity; } get loc(): CompilerErrorDetailOptions['loc'] { @@ -217,6 +313,12 @@ export class CompilerErrorDetail { get suggestions(): CompilerErrorDetailOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): LinterCategory | null | undefined { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].linterCategory; + } + return undefined; + } primaryLocation(): SourceLocation | null { return this.loc; @@ -266,7 +368,7 @@ export class CompilerError extends Error { static invariant( condition: unknown, - options: Omit, + options: Omit, ): asserts condition { if (!condition) { const errors = new CompilerError(); @@ -280,14 +382,14 @@ export class CompilerError extends Error { } } - static throwDiagnostic(options: CompilerDiagnosticOptions): never { + static throwDiagnostic(diagnostic: CompilerDiagnostic): never { const errors = new CompilerError(); - errors.pushDiagnostic(new CompilerDiagnostic(options)); + errors.pushDiagnostic(diagnostic); throw errors; } static throwTodo( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -296,34 +398,21 @@ export class CompilerError extends Error { throw errors; } - static throwInvalidJS( - options: Omit, + static throwFromCode( + code: ErrorCode, + options?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, ): never { const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidJS, - }), - ); - throw errors; - } - - static throwInvalidReact( - options: Omit, - ): never { - const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidReact, - }), - ); + errors.pushErrorDetail(CompilerErrorDetail.fromCode(code, options)); throw errors; } static throwInvalidConfig( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -392,13 +481,10 @@ export class CompilerError extends Error { } push(options: CompilerErrorDetailOptions): CompilerErrorDetail { - const detail = new CompilerErrorDetail({ - reason: options.reason, - description: options.description ?? null, - severity: options.severity, - suggestions: options.suggestions, - loc: typeof options.loc === 'symbol' ? null : options.loc, - }); + if (options instanceof CompilerErrorDetail) { + return this.pushErrorDetail(options); + } + const detail = new CompilerErrorDetail(options); return this.pushErrorDetail(detail); } @@ -407,6 +493,19 @@ export class CompilerError extends Error { return detail; } + pushErrorCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + const detail = CompilerErrorDetail.fromCode(code, details); + this.details.push(detail); + return detail; + } + hasErrors(): boolean { return this.details.length > 0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 24ce37cf72..ea1d28a62e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -38,10 +38,10 @@ export function validateRestrictedImports( ImportDeclaration(importDeclPath) { if (restrictedImports.has(importDeclPath.node.source.value)) { error.push({ - severity: ErrorSeverity.Todo, reason: 'Bailing out due to blocklisted import', description: `Import from module ${importDeclPath.node.source.value}`, loc: importDeclPath.node.loc ?? null, + severity: ErrorSeverity.Todo, }); } }, @@ -205,10 +205,10 @@ export class ProgramContext { } const error = new CompilerError(); error.push({ - severity: ErrorSeverity.Todo, reason: 'Encountered conflicting global in generated program', description: `Conflict from local binding ${name}`, loc: scope.getBinding(name)?.path.node.loc ?? null, + severity: ErrorSeverity.Todo, suggestions: null, }); return Err(error); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c13940ed10..1a8e1457ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerErrorDetail, - CompilerErrorDetailOptions, + PlainCompilerErrorDetailOptions, } from '../CompilerError'; import { EnvironmentConfig, @@ -107,6 +107,8 @@ export type PluginOptions = { * passes. * * Defaults to false + * + * TODO: rename this to lintOnly or something similar */ noEmit: boolean; @@ -234,7 +236,10 @@ export type CompileErrorEvent = { export type CompileDiagnosticEvent = { kind: 'CompileDiagnostic'; fnLoc: t.SourceLocation | null; - detail: Omit, 'suggestions'>; + detail: Omit< + Omit, + 'suggestions' + >; }; export type CompileSuccessEvent = { kind: 'CompileSuccess'; 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 a4f984f195..7a004fa18c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -100,7 +100,6 @@ import {outlineJSX} from '../Optimization/OutlineJsx'; import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls'; import {transformFire} from '../Transform'; import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender'; -import {CompilerError} from '..'; import {validateStaticComponents} from '../Validation/ValidateStaticComponents'; import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions'; import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects'; @@ -174,7 +173,7 @@ function runWithEnvironment( !env.config.disableMemoizationForDebugging && !env.config.enableChangeDetectionForDebugging ) { - dropManualMemoization(hir).unwrap(); + env.logOrThrowErrors(dropManualMemoization(hir)); log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } @@ -207,10 +206,10 @@ function runWithEnvironment( if (env.isInferredMemoEnabled) { if (env.config.validateHooksUsage) { - validateHooksUsage(hir).unwrap(); + env.logOrThrowErrors(validateHooksUsage(hir)); } - if (env.config.validateNoCapitalizedCalls) { - validateNoCapitalizedCalls(hir).unwrap(); + if (env.config.validateNoCapitalizedCalls != null) { + env.logOrThrowErrors(validateNoCapitalizedCalls(hir)); } } @@ -230,20 +229,17 @@ function runWithEnvironment( log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); if (!env.config.enableNewMutationAliasingModel) { - const fnEffectErrors = inferReferenceEffects(hir); + const fnEffectResult = inferReferenceEffects(hir); + if (env.isInferredMemoEnabled) { - if (fnEffectErrors.length > 0) { - CompilerError.throw(fnEffectErrors[0]); - } + env.logOrThrowErrors(fnEffectResult); } log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); } else { const mutabilityAliasingErrors = inferMutationAliasingEffects(hir); log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } + env.logOrThrowErrors(mutabilityAliasingErrors); } } @@ -272,10 +268,8 @@ function runWithEnvironment( }); log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } - validateLocalsNotReassignedAfterRender(hir); + env.logOrThrowErrors(mutabilityAliasingErrors.map(() => undefined)); + env.logOrThrowErrors(validateLocalsNotReassignedAfterRender(hir)); } } @@ -285,15 +279,15 @@ function runWithEnvironment( } if (env.config.validateRefAccessDuringRender) { - validateNoRefAccessInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoRefAccessInRender(hir)); } if (env.config.validateNoSetStateInRender) { - validateNoSetStateInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoSetStateInRender(hir)); } if (env.config.validateNoDerivedComputationsInEffects) { - validateNoDerivedComputationsInEffects(hir); + env.logOrThrowErrors(validateNoDerivedComputationsInEffects(hir)); } if (env.config.validateNoSetStateInEffects) { @@ -305,14 +299,14 @@ function runWithEnvironment( } if (env.config.validateNoImpureFunctionsInRender) { - validateNoImpureFunctionsInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoImpureFunctionsInRender(hir)); } if ( env.config.validateNoFreezingKnownMutableFunctions || env.config.enableNewMutationAliasingModel ) { - validateNoFreezingKnownMutableFunctions(hir).unwrap(); + env.logOrThrowErrors(validateNoFreezingKnownMutableFunctions(hir)); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 79bbee37a5..825f83a2a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -7,11 +7,7 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, ErrorSeverity} from '../CompilerError'; import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; @@ -32,6 +28,7 @@ import { } from './Suppression'; import {GeneratedSource} from '../HIR'; import {Err, Ok, Result} from '../Utils/Result'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; export type CompilerPass = { opts: PluginOptions; @@ -101,12 +98,9 @@ function findDirectivesDynamicGating( if (t.isValidIdentifier(maybeMatch[1])) { result.push({directive, match: maybeMatch[1]}); } else { - errors.push({ - reason: `Dynamic gating directive is not a valid JavaScript identifier`, + errors.pushErrorCode(ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, { description: `Found '${directive.value.value}'`, - severity: ErrorSeverity.InvalidReact, loc: directive.loc ?? null, - suggestions: null, }); } } @@ -115,14 +109,11 @@ function findDirectivesDynamicGating( return Err(errors); } else if (result.length > 1) { const error = new CompilerError(); - error.push({ - reason: `Multiple dynamic gating directives found`, + error.pushErrorCode(ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, { description: `Expected a single directive but found [${result .map(r => r.directive.value.value) .join(', ')}]`, - severity: ErrorSeverity.InvalidReact, loc: result[0].directive.loc ?? null, - suggestions: null, }); return Err(error); } else if (result.length === 1) { @@ -451,14 +442,12 @@ export function compileProgram( if (programContext.hasModuleScopeOptOut) { if (compiledFns.length > 0) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: - 'Unexpected compiled functions when module scope opt-out is present', - severity: ErrorSeverity.Invariant, - loc: null, - }), - ); + error.push({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }); handleError(error, programContext, null); } return null; @@ -591,9 +580,7 @@ function processFn( let compiledFn: CodegenFunction; const compileResult = tryCompileFunction(fn, fnType, programContext); if (compileResult.kind === 'error') { - if (directives.optOut != null) { - logError(compileResult.error, programContext, fn.node.loc ?? null); - } else { + if (directives.optOut == null) { handleError(compileResult.error, programContext, fn.node.loc ?? null); } const retryResult = retryCompileFunction(fn, fnType, programContext); @@ -692,7 +679,7 @@ function tryCompileFunction( fn, programContext.opts.environment, fnType, - 'all_features', + programContext.opts.noEmit ? 'lint_only' : 'all_features', programContext, programContext.opts.logger, programContext.filename, @@ -805,15 +792,7 @@ function shouldSkipCompilation( if (pass.opts.sources) { if (pass.filename === null) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: `Expected a filename but found none.`, - description: - "When the 'sources' config options is specified, the React compiler will only compile files with a name", - severity: ErrorSeverity.InvalidConfig, - loc: null, - }), - ); + error.pushErrorCode(ErrorCode.FILENAME_NOT_SET); handleError(error, pass, null); return true; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts index ee341b111f..75828aa108 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, - ErrorSeverity, + ErrorCode, } from '../CompilerError'; import {assertExhaustive} from '../Utils/utils'; import {GeneratedSource} from '../HIR'; @@ -165,14 +165,12 @@ export function suppressionsToCompilerError( let reason, suggestion; switch (suppressionRange.source) { case 'Eslint': - reason = - 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled'; + reason = ErrorCode.BAILOUT_ESLINT_SUPPRESSION; suggestion = 'Remove the ESLint suppression and address the React error'; break; case 'Flow': - reason = - 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow'; + reason = ErrorCode.BAILOUT_FLOW_SUPPRESSION; suggestion = 'Remove the Flow suppression and address the React error'; break; default: @@ -182,10 +180,8 @@ export function suppressionsToCompilerError( ); } error.pushDiagnostic( - CompilerDiagnostic.create({ - category: reason, - description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``, - severity: ErrorSeverity.InvalidReact, + CompilerDiagnostic.fromCode(reason, { + description: `Found suppression \`${suppressionRange.disableComment.value.trim()}\``, suggestions: [ { description: suggestion, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 16d7c3713c..5271d66886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -8,27 +8,24 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..'; +import {CompilerError, EnvironmentConfig, Logger} from '..'; import {getOrInsertWith} from '../Utils/utils'; import {Environment, GeneratedSource} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; import {CompileProgramMetadata} from './Program'; -import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError'; +import {CompilerDiagnostic} from '../CompilerError'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; -function throwInvalidReact( - options: Omit, +function logAndThrowDiagnostic( + diagnostic: CompilerDiagnostic, {logger, filename}: TraversalState, ): never { - const detail: CompilerDiagnosticOptions = { - severity: ErrorSeverity.InvalidReact, - ...options, - }; logger?.logEvent(filename, { kind: 'CompileError', fnLoc: null, - detail: new CompilerDiagnostic(detail), + detail: diagnostic, }); - CompilerError.throwDiagnostic(detail); + CompilerError.throwDiagnostic(diagnostic); } function isAutodepsSigil( @@ -90,10 +87,8 @@ function assertValidEffectImportReference( * as it may have already been transformed by the compiler (and not * memoized). */ - throwInvalidReact( - { - category: - 'Cannot infer dependencies of this effect. This will break your build!', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.DID_NOT_INFER_DEPS, { description: 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' + (maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''), @@ -104,7 +99,7 @@ function assertValidEffectImportReference( loc: parent.node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } @@ -121,10 +116,8 @@ function assertValidFireImportReference( paths[0], context.transformErrors, ); - throwInvalidReact( - { - category: - '[Fire] Untransformed reference to compiler-required feature.', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.CANNOT_COMPILE_FIRE, { description: 'Either remove this `fire` call or ensure it is successfully transformed by the compiler' + maybeErrorDiagnostic @@ -137,7 +130,7 @@ function assertValidFireImportReference( loc: paths[0].node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts b/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts index fa3f551ff5..85d6bd0457 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts @@ -1,4 +1,5 @@ import {CompilerError, SourceLocation} from '..'; +import {ErrorCode} from '../CompilerError'; import { ConcreteType, printConcrete, @@ -12,8 +13,8 @@ export function unsupportedLanguageFeature( desc: string, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Typedchecker does not currently support language feature: ${desc}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Typedchecker does not currently support language feature: ${desc}`, loc, }); } @@ -49,16 +50,16 @@ export function raiseUnificationErrors( loc, }); } else if (errs.length === 1) { - CompilerError.throwInvalidJS({ - reason: `Unable to unify types because ${printUnificationError(errs[0])}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to unify types because ${printUnificationError(errs[0])}`, loc, }); } else { const messages = errs .map(err => `\t* ${printUnificationError(err)}`) .join('\n'); - CompilerError.throwInvalidJS({ - reason: `Unable to unify types because:\n${messages}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to unify types because:\n${messages}`, loc, }); } @@ -69,21 +70,21 @@ export function unresolvableTypeVariable( id: VariableId, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Unable to resolve free variable ${id} to a concrete type`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to resolve free variable ${id} to a concrete type`, loc, }); } export function cannotAddVoid(explicit: boolean, loc: SourceLocation): never { if (explicit) { - CompilerError.throwInvalidJS({ - reason: `Undefined is not a valid operand of \`+\``, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Undefined is not a valid operand of \`+\``, loc, }); } else { - CompilerError.throwInvalidJS({ - reason: `Value may be undefined, which is not a valid operand of \`+\``, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Value may be undefined, which is not a valid operand of \`+\``, loc, }); } @@ -93,8 +94,8 @@ export function unsupportedTypeAnnotation( desc: string, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Typedchecker does not currently support type annotation: ${desc}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Typedchecker does not currently support type annotation: ${desc}`, loc, }); } @@ -106,16 +107,16 @@ export function checkTypeArgumentArity( loc: SourceLocation, ): void { if (expected !== actual) { - CompilerError.throwInvalidJS({ - reason: `Expected ${desc} to have ${expected} type parameters, got ${actual}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Expected ${desc} to have ${expected} type parameters, got ${actual}`, loc, }); } } export function notAFunction(desc: string, loc: SourceLocation): void { - CompilerError.throwInvalidJS({ - reason: `Cannot call ${desc} because it is not a function`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Cannot call ${desc} because it is not a function`, loc, }); } @@ -124,8 +125,8 @@ export function notAPolymorphicFunction( desc: string, loc: SourceLocation, ): void { - CompilerError.throwInvalidJS({ - reason: `Cannot call ${desc} with type arguments because it is not a polymorphic function`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Cannot call ${desc} with type arguments because it is not a polymorphic function`, loc, }); } 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 3b11670146..26f05f58ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -12,6 +12,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, + ErrorCode, ErrorSeverity, } from '../CompilerError'; import {Err, Ok, Result} from '../Utils/Result'; @@ -170,14 +171,12 @@ export function lower( ); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.Todo, - category: `Handle ${param.node.type} parameters`, + CompilerDiagnostic.fromCode(ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, { description: `[BuildHIR] Add support for ${param.node.type} parameters.`, }).withDetail({ kind: 'error', loc: param.node.loc ?? null, - message: 'Unsupported parameter type', + message: `Unsupported parameter type: ${param.node.type}`, }), ); } @@ -201,14 +200,12 @@ export function lower( directives = body.get('directives').map(d => d.node.value.value); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidJS, - category: `Unexpected function body kind`, + CompilerDiagnostic.fromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`, }).withDetail({ kind: 'error', loc: body.node.loc ?? null, - message: 'Expected a block statement or expression', + message: 'Change this to a block statement or expression', }), ); } @@ -769,12 +766,12 @@ function lowerStatement( const testExpr = case_.get('test'); if (testExpr.node == null) { if (hasDefault) { - builder.errors.push({ - reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, - severity: ErrorSeverity.InvalidJS, - loc: case_.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + { + loc: case_.node.loc ?? null, + }, + ); break; } hasDefault = true; @@ -886,19 +883,20 @@ function lowerStatement( if (builder.isContextIdentifier(id)) { if (kind === InstructionKind.Const) { const declRangeStart = declaration.parentPath.node.start!; - builder.errors.push({ - reason: `Expect \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: id.node.loc ?? null, - suggestions: [ - { - description: 'Change to a `let` declaration', - op: CompilerSuggestionOperation.Replace, - range: [declRangeStart, declRangeStart + 5], // "const".length - text: 'let', - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: id.node.loc ?? null, + suggestions: [ + { + description: 'Change to a `let` declaration', + op: CompilerSuggestionOperation.Replace, + range: [declRangeStart, declRangeStart + 5], // "const".length + text: 'let', + }, + ], + }, + ); } lowerValueToTemporary(builder, { kind: 'DeclareContext', @@ -932,13 +930,13 @@ function lowerStatement( } } } else { - builder.errors.push({ - reason: `Expected variable declaration to be an identifier if no initializer was provided`, - description: `Got a \`${id.type}\``, - severity: ErrorSeverity.InvalidJS, - loc: stmt.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + { + description: `Got a \`${id.type}\``, + loc: stmt.node.loc ?? null, + }, + ); } } return; @@ -1374,12 +1372,8 @@ function lowerStatement( return; } case 'WithStatement': { - builder.errors.push({ - reason: `JavaScript 'with' syntax is not supported`, - description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_WITH, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1394,12 +1388,8 @@ function lowerStatement( * and complex enough to support that we don't anticipate supporting anytime soon. Developers * are encouraged to lift classes out of component/hook declarations. */ - builder.errors.push({ - reason: 'Inline `class` declarations are not supported', - description: `Move class declarations outside of components/hooks`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_INNER_CLASS, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1423,12 +1413,8 @@ function lowerStatement( case 'ImportDeclaration': case 'TSExportAssignment': case 'TSImportEqualsDeclaration': { - builder.errors.push({ - reason: - 'JavaScript `import` and `export` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_IMPORT_EXPORT, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1438,12 +1424,8 @@ function lowerStatement( return; } case 'TSNamespaceExportDeclaration': { - builder.errors.push({ - reason: - 'TypeScript `namespace` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_TS_NAMESPACE, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1698,12 +1680,8 @@ function lowerExpression( const expr = exprPath as NodePath; const calleePath = expr.get('callee'); if (!calleePath.isExpression()) { - builder.errors.push({ - reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, - description: `Got a \`${calleePath.node.type}\``, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_NEW_EXPRESSION, { loc: calleePath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -1800,12 +1778,12 @@ function lowerExpression( last = lowerExpressionToTemporary(builder, item); } if (last === null) { - builder.errors.push({ - reason: `Expected sequence expression to have at least one expression`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + { + loc: expr.node.loc ?? null, + }, + ); } else { lowerValueToTemporary(builder, { kind: 'StoreLocal', @@ -2289,18 +2267,18 @@ function lowerExpression( }); for (const [name, locations] of Object.entries(fbtLocations)) { if (locations.length > 1) { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support duplicate fbt tags', - description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, - details: locations.map(loc => { - return { - kind: 'error', - message: `Multiple \`<${tagName}:${name}>\` tags found`, - loc, - }; + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_DUPLICATE_FBT_TAGS, { + description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, + details: locations.map(loc => { + return { + kind: 'error', + message: `Multiple \`<${tagName}:${name}>\` tags found`, + loc, + }; + }), }), - }); + ); } } } @@ -2389,11 +2367,8 @@ function lowerExpression( const quasis = expr.get('quasis'); if (subexprs.length !== quasis.length - 1) { - builder.errors.push({ - reason: `Unexpected quasi and subexpression lengths in template literal`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_QUASI_LENGTHS, { loc: exprPath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -2441,24 +2416,23 @@ function lowerExpression( }; } } else { - builder.errors.push({ - reason: `Only object properties can be deleted`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: [ - { - description: 'Remove this line', - range: [expr.node.start!, expr.node.end!], - op: CompilerSuggestionOperation.Remove, - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + { + loc: expr.node.loc ?? null, + suggestions: [ + { + description: 'Remove this line', + range: [expr.node.start!, expr.node.end!], + op: CompilerSuggestionOperation.Remove, + }, + ], + }, + ); return {kind: 'UnsupportedNode', node: expr.node, loc: exprLoc}; } } else if (expr.node.operator === 'throw') { - builder.errors.push({ - reason: `Throw expressions are not supported`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_THROW_EXPRESSION, { loc: expr.node.loc ?? null, suggestions: [ { @@ -3285,10 +3259,8 @@ function lowerJsxElementName( const name = exprPath.node.name.name; const tag = `${namespace}:${name}`; if (namespace.indexOf(':') !== -1 || name.indexOf(':') !== -1) { - builder.errors.push({ - reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + builder.errors.pushErrorCode(ErrorCode.INVALID_JSX_NAMESPACED_NAME, { description: `Got \`${namespace}\` : \`${name}\``, - severity: ErrorSeverity.InvalidJS, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3583,11 +3555,7 @@ function lowerIdentifier( } default: { if (binding.kind === 'Global' && binding.name === 'eval') { - builder.errors.push({ - reason: `The 'eval' function is not supported`, - description: - 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_EVAL, { loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3653,9 +3621,7 @@ function lowerIdentifierForAssignment( binding.bindingKind === 'const' && kind === InstructionKind.Reassign ) { - builder.errors.push({ - reason: `Cannot reassign a \`const\` variable`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, { loc: path.node.loc ?? null, description: binding.identifier.name != null @@ -3710,12 +3676,13 @@ function lowerAssignment( let temporary; if (builder.isContextIdentifier(lvalue)) { if (kind === InstructionKind.Const && !isHoistedIdentifier) { - builder.errors.push({ - reason: `Expected \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: lvalue.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: lvalue.node.loc ?? null, + suggestions: null, + }, + ); } if ( @@ -3726,7 +3693,8 @@ function lowerAssignment( ) { builder.errors.push({ reason: `Unexpected context variable kind`, - severity: ErrorSeverity.InvalidJS, + description: `Expected one of Const, Reassign, Let, Function, got ${kind}`, + severity: ErrorSeverity.Invariant, loc: lvalue.node.loc ?? null, suggestions: null, }); 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 957c5ab84a..ad1815a62d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -93,7 +93,7 @@ export const MacroSchema = z.union([ z.tuple([z.string(), z.array(MacroMethodSchema)]), ]); -export type CompilerMode = 'all_features' | 'no_inferred_memo'; +export type CompilerMode = 'all_features' | 'no_inferred_memo' | 'lint_only'; export type Macro = z.infer; export type MacroMethod = z.infer; @@ -829,6 +829,14 @@ export class Environment { } } + logOrThrowErrors(errors: Result): void { + if (this.compilerMode === 'lint_only') { + this.logErrors(errors); + } else { + errors.unwrap(); + } + } + isContextIdentifier(node: t.Identifier): boolean { return this.#contextIdentifiers.has(node); } 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 51715b3c1e..89b2c0fc41 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -15,6 +15,7 @@ import {Type, makeType} from './Types'; import {z} from 'zod'; import type {AliasingEffect} from '../Inference/AliasingEffects'; import {isReservedWord} from '../Utils/Keyword'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -1322,18 +1323,18 @@ export function forkTemporaryIdentifier( */ export function makeIdentifierName(name: string): ValidatedIdentifier { if (isReservedWord(name)) { - CompilerError.throwInvalidJS({ - reason: 'Expected a non-reserved identifier name', - loc: GeneratedSource, - description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, - suggestions: null, - }); + CompilerError.throwFromCode( + ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + { + loc: GeneratedSource, + description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, + }, + ); } else { CompilerError.invariant(t.isValidIdentifier(name), { reason: `Expected a valid identifier name`, loc: GeneratedSource, description: `\`${name}\` is not a valid JavaScript identifier`, - suggestions: null, }); } return { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 81959ea361..1d71452d0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -7,7 +7,7 @@ import {Binding, NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {Environment} from './Environment'; import { BasicBlock, @@ -37,6 +37,7 @@ import { mapTerminalSuccessors, terminalFallthrough, } from './visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -308,19 +309,17 @@ export default class HIRBuilder { resolveBinding(node: t.Identifier): Identifier { if (node.name === 'fbt') { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support local variables named `fbt`', - description: - 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', - details: [ - { - kind: 'error', - message: 'Rename to avoid conflict with fbt plugin', - loc: node.loc ?? GeneratedSource, - }, - ], - }); + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, { + details: [ + { + kind: 'error', + message: 'Rename to avoid conflict with fbt plugin', + loc: node.loc ?? GeneratedSource, + }, + ], + }), + ); } const originalName = node.name; let name = originalName; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 7aeb3edb22..fa9bddd3f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -5,12 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, - SourceLocation, -} from '..'; +import {CompilerDiagnostic, CompilerError, SourceLocation} from '..'; import { CallExpression, Effect, @@ -35,6 +30,7 @@ import { makeInstructionId, } from '../HIR'; import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; type ManualMemoCallee = { @@ -299,12 +295,11 @@ function extractManualMemoizationArgs( >; if (fnPlace == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected a callback function to be passed to ${kind}`, - description: `Expected a callback function to be passed to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + kind === 'useMemo' + ? ErrorCode.INVALID_USE_MEMO_NO_ARG0 + : ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Expected a callback function to be passed to ${kind}`, @@ -314,12 +309,11 @@ function extractManualMemoizationArgs( } if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Unexpected spread argument to ${kind}`, - description: `Unexpected spread argument to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + fnPlace.kind === 'Spread' + ? ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT + : ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Unexpected spread argument to ${kind}`, @@ -334,12 +328,9 @@ function extractManualMemoizationArgs( ); if (maybeDepsList == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list for ${kind} to be an array literal`, - description: `Expected the dependency list for ${kind} to be an array literal`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + ).withDetail({ kind: 'error', loc: depsListPlace.loc, message: `Expected the dependency list for ${kind} to be an array literal`, @@ -352,12 +343,9 @@ function extractManualMemoizationArgs( const maybeDep = sidemap.maybeDeps.get(dep.identifier.id); if (maybeDep == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + ).withDetail({ kind: 'error', loc: dep.loc, message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, @@ -457,16 +445,16 @@ export function dropManualMemoization( if (funcToCheck !== undefined && funcToCheck.loweredFunc.func) { if (!hasNonVoidReturn(funcToCheck.loweredFunc.func)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks must return a value', - description: `This ${ - manualMemo.loadInstr.value.kind === 'PropertyLoad' - ? 'React.useMemo' - : 'useMemo' - } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + { + description: `This ${ + manualMemo.loadInstr.value.kind === 'PropertyLoad' + ? 'React.useMemo' + : 'useMemo' + } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, + }, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: 'useMemo() callbacks must return a value', @@ -497,12 +485,9 @@ export function dropManualMemoization( */ if (!sidemap.functions.has(fnPlace.identifier.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the first argument to be an inline function expression`, - description: `Expected the first argument to be an inline function expression`, - suggestions: [], - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + ).withDetail({ kind: 'error', loc: fnPlace.loc, message: `Expected the first argument to be an inline function expression`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts index a01ca188a0..5fcf8a3cae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts @@ -25,6 +25,7 @@ import { isRefOrRefValue, } from '../HIR'; import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {assertExhaustive} from '../Utils/utils'; interface State { @@ -62,22 +63,20 @@ function inferOperandEffect(state: State, place: Place): null | FunctionEffect { // We ignore mutations of primitives since this is not a React-specific problem value.kind !== ValueKind.Primitive ) { - let reason = getWriteErrorReason(value); + let errorCode = getWriteErrorReason(value); return { kind: value.reason.size === 1 && value.reason.has(ValueReason.Global) ? 'GlobalMutation' : 'ReactMutation', error: { - reason, + errorCode, description: place.identifier.name !== null && place.identifier.name.kind === 'named' ? `Found mutation of \`${place.identifier.name.value}\`` : null, loc: place.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }; } @@ -266,11 +265,8 @@ export function inferInstructionFunctionEffects( functionEffects.push({ kind: 'GlobalMutation', error: { - reason: - 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + errorCode: ErrorCode.INVALID_WRITE_GLOBAL, loc: instr.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }); break; @@ -324,28 +320,28 @@ function isEffectSafeOutsideRender(effect: FunctionEffect): boolean { return effect.kind === 'GlobalMutation'; } -export function getWriteErrorReason(abstractValue: AbstractValue): string { +export function getWriteErrorReason(abstractValue: AbstractValue): ErrorCode { if (abstractValue.reason.has(ValueReason.Global)) { - return 'Modifying a variable defined outside a component or hook is not allowed. Consider using an effect'; + return ErrorCode.INVALID_WRITE_GLOBAL; } else if (abstractValue.reason.has(ValueReason.JsxCaptured)) { - return 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX'; + return ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX; } else if (abstractValue.reason.has(ValueReason.Context)) { - return `Modifying a value returned from 'useContext()' is not allowed.`; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT; } else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) { - return 'Modifying a value returned from a function whose return value should not be mutated'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE; } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) { - return 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS; } else if (abstractValue.reason.has(ValueReason.State)) { - return "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead"; + return ErrorCode.INVALID_WRITE_STATE; } else if (abstractValue.reason.has(ValueReason.ReducerState)) { - return "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead"; + return ErrorCode.INVALID_WRITE_REDUCER_STATE; } else if (abstractValue.reason.has(ValueReason.Effect)) { - return 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()'; + return ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY; } else if (abstractValue.reason.has(ValueReason.HookCaptured)) { - return 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook'; + return ErrorCode.INVALID_WRITE_HOOK_CAPTURED; } else if (abstractValue.reason.has(ValueReason.HookReturn)) { - return 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed'; + return ErrorCode.INVALID_WRITE_HOOK_RETURN; } else { - return 'This modifies a variable that React considers immutable'; + return ErrorCode.INVALID_WRITE_GENERIC; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index 2adf78fe05..60146d8335 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -9,7 +9,6 @@ import { CompilerDiagnostic, CompilerError, Effect, - ErrorSeverity, SourceLocation, ValueKind, } from '..'; @@ -69,6 +68,7 @@ import {getWriteErrorReason} from './InferFunctionEffects'; import prettyFormat from 'pretty-format'; import {createTemporaryPlace} from '../HIR/HIRBuilder'; import {AliasingEffect, AliasingSignature, hashEffect} from './AliasingEffects'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; const DEBUG = false; @@ -442,7 +442,7 @@ function applySignature( const value = state.kind(effect.value); switch (value.kind) { case ValueKind.Frozen: { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -455,10 +455,9 @@ function applySignature( effects.push({ kind: 'MutateFrozen', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -1026,22 +1025,20 @@ function applyEffect( const hoistedAccess = context.hoistedContextDeclarations.get( effect.value.identifier.declarationId, ); - const diagnostic = CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access variable before it is declared', - description: `${variable ?? 'This variable'} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.`, - }); + const diagnostic = CompilerDiagnostic.fromCode( + ErrorCode.INVALID_ACCESS_BEFORE_INIT, + ); if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) { diagnostic.withDetail({ kind: 'error', loc: hoistedAccess.loc, - message: `${variable ?? 'variable'} accessed before it is declared`, + message: `${variable ?? 'This variable'} is accessed before it is declared`, }); } diagnostic.withDetail({ kind: 'error', loc: effect.value.loc, - message: `${variable ?? 'variable'} is declared here`, + message: `${variable ?? 'This variable'} is declared here`, }); applyEffect( @@ -1056,7 +1053,7 @@ function applyEffect( effects, ); } else { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -1075,10 +1072,9 @@ function applyEffect( ? 'MutateFrozen' : 'MutateGlobal', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -2006,15 +2002,12 @@ function computeSignatureForInstruction( effects.push({ kind: 'MutateGlobal', place: value.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'Cannot reassign variables declared outside of the component/hook', - description: `Variable ${variable} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)`, - }).withDetail({ + error: CompilerDiagnostic.fromCode( + ErrorCode.INVALID_WRITE_GLOBAL, + ).withDetail({ kind: 'error', loc: instr.loc, - message: `${variable} cannot be reassigned`, + message: `${variable} should not be reassigned`, }), }); effects.push({kind: 'Assign', from: value.value, into: lvalue}); @@ -2105,19 +2098,16 @@ function computeEffectsForLegacySignature( effects.push({ kind: 'Impure', place: receiver, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - }).withDetail({ - kind: 'error', - loc, - message: 'Cannot call impure function', - }), + error: CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail( + { + kind: 'error', + loc, + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', + }, + ), }); } const stores: Array = []; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 1b0856791a..a806ebd4bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError'; +import {CompilerError} from '../CompilerError'; import {Environment} from '../HIR'; import { AbstractValue, @@ -48,6 +48,7 @@ import { eachTerminalOperand, eachTerminalSuccessor, } from '../HIR/visitors'; +import {Err, Ok, Result} from '../Utils/Result'; import {assertExhaustive, Set_isSuperset} from '../Utils/utils'; import { inferTerminalFunctionEffects, @@ -106,7 +107,7 @@ const UndefinedValue: InstructionValue = { export default function inferReferenceEffects( fn: HIRFunction, options: {isFunctionExpression: boolean} = {isFunctionExpression: false}, -): Array { +): Result { /* * Initial state contains function params * TODO: include module declarations here as well @@ -247,10 +248,17 @@ export default function inferReferenceEffects( if (options.isFunctionExpression) { fn.effects = functionEffects; - return []; } else { - return transformFunctionEffectErrors(functionEffects); + const errors = transformFunctionEffectErrors(functionEffects); + if (errors.length > 0) { + const compilerError = new CompilerError(); + for (const detail of errors) { + compilerError.push(detail); + } + return Err(compilerError); + } } + return Ok(void 0); } type FreezeAction = {values: Set; reason: Set}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts index f88c85f2f0..dbb84944b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts @@ -42,6 +42,7 @@ import { import {eachInstructionOperand} from '../HIR/visitors'; import {printSourceLocationLine} from '../HIR/PrintHIR'; import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * TODO(jmbrown): @@ -50,8 +51,6 @@ import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; * - React.useEffect calls */ -const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`'; - export function transformFire(fn: HIRFunction): void { const context = new Context(fn.env); replaceFireFunctions(fn, context); @@ -178,9 +177,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } else if (value.args.length > 1 && value.args[1].kind === 'Spread') { @@ -188,9 +185,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].place.loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -243,11 +238,9 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } else { context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description: '`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed', - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else { @@ -262,10 +255,8 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else if (value.kind === 'CallExpression') { @@ -394,9 +385,7 @@ function ensureNoRemainingCalleeCaptures( description: `All uses of ${calleeName} must be either used with a fire() call in \ this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \ ${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -411,9 +400,7 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { context.pushError({ loc: place.identifier.loc, description: 'Cannot use `fire` outside of a useEffect function', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts new file mode 100644 index 0000000000..2fd3244fc5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts @@ -0,0 +1,611 @@ +import {ErrorSeverity} from './CompilerErrorSeverity'; + +export enum LinterCategory { + RULES_OF_HOOKS = 'rules-of-hooks', + IMPURE_FUNCTIONS = 'impure-functions', + JSX_IN_TRY = 'jsx-in-try', + NO_REF_ACCESS_IN_RENDER = 'no-ref-access-in-render', + VALIDATE_MANUAL_MEMO = 'validate-manual-memo', + DYNAMIC_MANUAL_MEMO = 'dynamic-manual-memo', + + EXHAUSTIVE_DEPS = 'exhaustive-deps', + MEMOIZED_DEPENDENCIES = 'memoized-dependencies', // not real! + INVALID_WRITE = 'invalid-write', + CAPITALIZED_CALLS = 'capitalized-calls', + STATIC_COMPONENTS = 'static-components', + NO_SET_STATE_IN_RENDER = 'no-set-state-in-render', + NO_SET_STATE_IN_EFFECTS = 'no-set-state-in-effects', + UNNECESSARY_EFFECTS = 'unnecessary-effects', + + UNSUPPORTED_SYNTAX = 'unsupported-syntax', + + TODO_SYNTAX = 'todo-syntax', + + COMPILER_CONFIG = 'compiler-config', +} + +export enum ErrorCode { + HOOK_CALL_STATIC, + HOOK_INVALID_REFERENCE, + HOOK_CALL_REACTIVE, + HOOK_CALL_NOT_TOP_LEVEL, + IMPURE_FUNCTIONS, + JSX_IN_TRY, + NO_REF_ACCESS_IN_RENDER, + WRITE_AFTER_RENDER, + REASSIGN_IN_ASYNC, + INVALID_WRITE, + CAPITALIZED_CALLS, + STATIC_COMPONENTS, + INVALID_USE_MEMO_CALLBACK_RETURN, + INVALID_USE_MEMO_CALLBACK_PARAMETERS, + INVALID_USE_MEMO_CALLBACK_ASYNC, + INVALID_USE_MEMO_NO_ARG0, + INVALID_USE_CALLBACK_NO_ARG0, + DYNAMIC_MANUAL_MEMO_CALLBACK, + DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + + INVALID_SET_STATE_IN_RENDER, + INVALID_SET_STATE_IN_MEMO, + INVALID_SET_STATE_IN_EFFECTS, + + NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + + DYNAMIC_GATING_IS_NOT_IDENTIFIER, + DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + FILENAME_NOT_SET, + + INVALID_WRITE_GLOBAL, + INVALID_WRITE_FROZEN_VALUE_JSX, + INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + INVALID_WRITE_IMMUTABLE_ARGS, + INVALID_WRITE_STATE, + INVALID_WRITE_REDUCER_STATE, + INVALID_WRITE_EFFECT_DEPENDENCY, + INVALID_WRITE_HOOK_CAPTURED, + INVALID_WRITE_HOOK_RETURN, + INVALID_ACCESS_BEFORE_INIT, + INVALID_WRITE_GENERIC, + + MEMOIZED_EFFECT_DEPENDENCIES, + + INVALID_SYNTAX_MULTIPLE_DEFAULTS, + INVALID_SYNTAX_REASSIGNED_CONST, + INVALID_SYNTAX_BAD_VARIABLE_DECL, + UNSUPPORTED_WITH, + UNSUPPORTED_INNER_CLASS, + INVALID_IMPORT_EXPORT, + INVALID_TS_NAMESPACE, + UNSUPPORTED_NEW_EXPRESSION, + UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + INVALID_QUASI_LENGTHS, + INVALID_SYNTAX_DELETE_EXPRESSION, + UNSUPPORTED_THROW_EXPRESSION, + INVALID_JSX_NAMESPACED_NAME, + UNSUPPORTED_EVAL, + INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + INVALID_JAVASCRIPT_AST, + BAILOUT_ESLINT_SUPPRESSION, + BAILOUT_FLOW_SUPPRESSION, + MANUAL_MEMO_MUTATED_LATER, + MANUAL_MEMO_REMOVED, + MANUAL_MEMO_DEPENDENCIES_CONFLICT, + + CANNOT_COMPILE_FIRE, + DID_NOT_INFER_DEPS, + + /** Todo syntax */ + TODO_CONFLICTING_FBT_IDENTIFIER, + TODO_DUPLICATE_FBT_TAGS, + UNKNOWN_FUNCTION_PARAMETERS, +} + +type ErrorCodeType = { + code: ErrorCode; + description?: string; + severity: ErrorSeverity; + reason: string; + linterCategory: LinterCategory | null; +}; + +export const ErrorCodeDetails: Record = { + [ErrorCode.HOOK_CALL_STATIC]: { + code: ErrorCode.HOOK_CALL_STATIC, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_INVALID_REFERENCE]: { + code: ErrorCode.HOOK_INVALID_REFERENCE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_REACTIVE]: { + code: ErrorCode.HOOK_CALL_REACTIVE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_NOT_TOP_LEVEL]: { + code: ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.IMPURE_FUNCTIONS]: { + code: ErrorCode.IMPURE_FUNCTIONS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot call impure functions during render', + description: + 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).', + linterCategory: LinterCategory.IMPURE_FUNCTIONS, + }, + [ErrorCode.JSX_IN_TRY]: { + code: ErrorCode.JSX_IN_TRY, + severity: ErrorSeverity.InvalidReact, + reason: 'Avoid constructing JSX within try/catch', + description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, + linterCategory: LinterCategory.JSX_IN_TRY, + }, + [ErrorCode.NO_REF_ACCESS_IN_RENDER]: { + code: ErrorCode.NO_REF_ACCESS_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access refs during render', + description: + 'React refs are values that are not needed for rendering. Refs should only be accessed ' + + 'outside of render, such as in event handlers or effects. ' + + 'Accessing a ref value (the `current` property) during render can cause your component ' + + 'not to update as expected (https://react.dev/reference/react/useRef)', + linterCategory: LinterCategory.NO_REF_ACCESS_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_EFFECTS]: { + code: ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Calling setState synchronously within an effect can trigger cascading renders', + description: + 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + + 'In general, the body of an effect should do one or both of the following:\n' + + '* Update external systems with the latest state from React.\n' + + '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + + 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + + '(https://react.dev/learn/you-might-not-need-an-effect)', + linterCategory: LinterCategory.NO_SET_STATE_IN_EFFECTS, + }, + [ErrorCode.WRITE_AFTER_RENDER]: { + code: ErrorCode.WRITE_AFTER_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot modify local variables after render completes', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.REASSIGN_IN_ASYNC]: { + code: ErrorCode.REASSIGN_IN_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variable in async function', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE]: { + code: ErrorCode.INVALID_WRITE, + severity: ErrorSeverity.InvalidReact, + reason: 'This value cannot be modified', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.CAPITALIZED_CALLS]: { + code: ErrorCode.CAPITALIZED_CALLS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config', + linterCategory: LinterCategory.CAPITALIZED_CALLS, + }, + [ErrorCode.STATIC_COMPONENTS]: { + code: ErrorCode.STATIC_COMPONENTS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot create components during render', + linterCategory: LinterCategory.STATIC_COMPONENTS, + }, + [ErrorCode.INVALID_USE_MEMO_NO_ARG0]: { + code: ErrorCode.INVALID_USE_MEMO_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useMemo`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_CALLBACK_NO_ARG0]: { + code: ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useCallback`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useMemo', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + + [ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useCallback', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not accept parameters', + description: + 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not be async or generator functions', + description: + 'useMemo() callbacks are called once and must synchronously return a value.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks must return a value', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the first argument to be an inline function expression`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list of useMemo or useCallback to be an array literal`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY]: { + code: ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_SET_STATE_IN_RENDER]: { + code: ErrorCode.INVALID_SET_STATE_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState during render may trigger an infinite loop', + description: + 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_MEMO]: { + code: ErrorCode.INVALID_SET_STATE_IN_MEMO, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState from useMemo may trigger an infinite loop', + description: + 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS]: { + code: ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', + linterCategory: LinterCategory.UNNECESSARY_EFFECTS, + }, + + /** Invalid writes */ + [ErrorCode.INVALID_WRITE_GLOBAL]: { + code: ErrorCode.INVALID_WRITE_GLOBAL, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variables declared outside of the component/hook', + description: + 'Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX]: { + code: ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + severity: ErrorSeverity.InvalidReact, + reason: `Modifying a value returned from 'useContext()' is not allowed.`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a function whose return value should not be mutated', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_STATE]: { + code: ErrorCode.INVALID_WRITE_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_REDUCER_STATE]: { + code: ErrorCode.INVALID_WRITE_REDUCER_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY]: { + code: ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_CAPTURED]: { + code: ErrorCode.INVALID_WRITE_HOOK_CAPTURED, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_RETURN]: { + code: ErrorCode.INVALID_WRITE_HOOK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_ACCESS_BEFORE_INIT]: { + code: ErrorCode.INVALID_ACCESS_BEFORE_INIT, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access variable before it is declared', + description: `Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_GENERIC]: { + code: ErrorCode.INVALID_WRITE_GENERIC, + severity: ErrorSeverity.InvalidReact, + reason: 'This modifies a variable that React considers immutable', + linterCategory: LinterCategory.INVALID_WRITE, + }, + + /** Compiler Config */ + [ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER]: { + code: ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, + severity: ErrorSeverity.InvalidReact, + reason: 'Dynamic gating directive is not a valid JavaScript identifier', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES]: { + code: ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + severity: ErrorSeverity.InvalidReact, + reason: 'Expected a single dynamic gating directive', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.FILENAME_NOT_SET]: { + code: ErrorCode.FILENAME_NOT_SET, + severity: ErrorSeverity.InvalidConfig, + reason: `Expected a filename but found none.`, + description: + "When the 'sources' config options is specified, the React compiler will only compile files with a name", + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + + /** Effect dependencies */ + [ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES]: { + code: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, + reason: + 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.MEMOIZED_DEPENDENCIES, + }, + + /** Invalid / unsupported syntax */ + [ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS]: { + code: ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST]: { + code: ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + reason: `Expect \`const\` declaration not to be reassigned`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL]: { + code: ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + reason: `Expected variable declaration to be an identifier if no initializer was provided`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_WITH]: { + code: ErrorCode.UNSUPPORTED_WITH, + reason: `JavaScript 'with' syntax is not supported`, + description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_INNER_CLASS]: { + code: ErrorCode.UNSUPPORTED_INNER_CLASS, + reason: 'Inline `class` declarations are not supported', + description: `Move class declarations outside of components/hooks`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_IMPORT_EXPORT]: { + code: ErrorCode.INVALID_IMPORT_EXPORT, + reason: + 'JavaScript `import` and `export` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_TS_NAMESPACE]: { + code: ErrorCode.INVALID_TS_NAMESPACE, + reason: + 'TypeScript `namespace` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_NEW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_NEW_EXPRESSION, + reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + reason: `Expected sequence expression to have at least one expression`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_QUASI_LENGTHS]: { + code: ErrorCode.INVALID_QUASI_LENGTHS, + reason: `Unexpected quasi and subexpression lengths in template literal`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION]: { + code: ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + reason: `Only object properties can be deleted`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_THROW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_THROW_EXPRESSION, + reason: `Throw expressions are not supported`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JSX_NAMESPACED_NAME]: { + code: ErrorCode.INVALID_JSX_NAMESPACED_NAME, + reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EVAL]: { + code: ErrorCode.UNSUPPORTED_EVAL, + reason: `The 'eval' function is not supported`, + description: + 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME]: { + code: ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + reason: 'Expected a non-reserved identifier name', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JAVASCRIPT_AST]: { + code: ErrorCode.INVALID_JAVASCRIPT_AST, + reason: 'Encountered invalid JavaScript', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.BAILOUT_ESLINT_SUPPRESSION]: { + code: ErrorCode.BAILOUT_ESLINT_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_REMOVED]: { + code: ErrorCode.MANUAL_MEMO_REMOVED, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + + /** + * This is left vague as fire is very experimental + */ + [ErrorCode.CANNOT_COMPILE_FIRE]: { + code: ErrorCode.CANNOT_COMPILE_FIRE, + reason: 'Cannot compile `fire`', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.DID_NOT_INFER_DEPS]: { + code: ErrorCode.DID_NOT_INFER_DEPS, + reason: + 'Cannot infer dependencies of this effect. This will break your build!', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + [ErrorCode.BAILOUT_FLOW_SUPPRESSION]: { + code: ErrorCode.BAILOUT_FLOW_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + /** Syntax React Compiler may eventually support */ + [ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER]: { + code: ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, + reason: 'Support local variables named `fbt`', + description: + 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.TODO_DUPLICATE_FBT_TAGS]: { + code: ErrorCode.TODO_DUPLICATE_FBT_TAGS, + reason: 'Support duplicate fbt tags', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.UNKNOWN_FUNCTION_PARAMETERS]: { + code: ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, + severity: ErrorSeverity.Todo, + reason: 'Currently unsupported function parameter syntax', + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_MUTATED_LATER]: { + code: ErrorCode.MANUAL_MEMO_MUTATED_LATER, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: [ + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', + 'This dependency may be mutated later, which could cause the value to change unexpectedly.', + ].join(''), + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT]: { + code: ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + '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.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts new file mode 100644 index 0000000000..f399cadb9d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts @@ -0,0 +1,34 @@ +export enum ErrorSeverity { + /** + * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some + * misunderstanding on the user’s part. + */ + InvalidJS = 'InvalidJS', + /** + * JS syntax that is not supported and which we do not plan to support. Developers should + * rewrite to use supported forms. + */ + UnsupportedJS = 'UnsupportedJS', + /** + * Code that breaks the rules of React. + */ + InvalidReact = 'InvalidReact', + /** + * Incorrect configuration of the compiler. + */ + InvalidConfig = 'InvalidConfig', + /** + * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve + * memoization. + */ + CannotPreserveMemoization = 'CannotPreserveMemoization', + /** + * Unhandled syntax that we don't support yet. + */ + Todo = 'Todo', + /** + * An unexpected internal error in the compiler that indicates critical issues that can panic + * the compiler. + */ + Invariant = 'Invariant', +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts index b28228339c..d04e163960 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts @@ -6,11 +6,7 @@ */ import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, CompilerErrorDetail} from '../CompilerError'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {isHookName} from '../HIR/Environment'; import { @@ -27,6 +23,7 @@ import { } from '../HIR/visitors'; import {assertExhaustive} from '../Utils/utils'; import {Result} from '../Utils/Result'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; /** * Represents the possible kinds of value which may be stored at a given Place during @@ -111,8 +108,6 @@ export function validateHooksUsage( // Once a particular hook has a conditional call error, don't report any further issues for this hook setKind(place, Kind.Error); - const reason = - 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)'; const previousError = typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined; @@ -120,15 +115,15 @@ export function validateHooksUsage( * In some circumstances such as optional calls, we may first encounter a "hook may not be referenced as normal values" error. * If that same place is also used as a conditional call, upgrade the error to a conditonal hook error */ - if (previousError === undefined || previousError.reason !== reason) { + if ( + previousError === undefined || + previousError.reason !== + ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason + ) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason, + CompilerErrorDetail.fromCode(ErrorCode.HOOK_CALL_STATIC, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -139,13 +134,8 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + CompilerErrorDetail.fromCode(ErrorCode.HOOK_INVALID_REFERENCE, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -156,14 +146,12 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', - loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }), + CompilerErrorDetail.fromCode( + ErrorCodeDetails[ErrorCode.HOOK_CALL_REACTIVE].code, + { + loc: place.loc, + }, + ), ); } } @@ -424,7 +412,7 @@ export function validateHooksUsage( } for (const [, error] of errorsByPlace) { - errors.push(error); + errors.pushErrorDetail(error); } return errors.asResult(); } @@ -446,16 +434,10 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void { : instr.value.property; const hookKind = getHookKind(fn.env, callee.identifier); if (hookKind != null) { - errors.pushErrorDetail( - new CompilerErrorDetail({ - severity: ErrorSeverity.InvalidReact, - reason: - 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', - loc: callee.loc, - description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, - suggestions: null, - }), - ); + errors.pushErrorCode(ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, { + loc: callee.loc, + description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, + }); } break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts index 31bbf8c94d..b6c3038915 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; import {HIRFunction, IdentifierId, Place} from '../HIR'; import { eachInstructionLValue, @@ -13,13 +13,17 @@ import { eachTerminalOperand, } from '../HIR/visitors'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Ok, Result} from '../Utils/Result'; /** * Validates that local variables cannot be reassigned after render. * This prevents a category of bugs in which a closure captures a * binding from one render but does not update */ -export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { +export function validateLocalsNotReassignedAfterRender( + fn: HIRFunction, +): Result { const contextVariables = new Set(); const reassignment = getContextReassignment( fn, @@ -35,9 +39,7 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable after render completes', + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER, { description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }).withDetail({ kind: 'error', @@ -45,8 +47,9 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { message: `Cannot reassign ${variable} after render completes`, }), ); - throw errors; + return errors.asResult(); } + return Ok(undefined); } function getContextReassignment( @@ -90,9 +93,7 @@ function getContextReassignment( ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable in async function', + CompilerDiagnostic.fromCode(ErrorCode.REASSIGN_IN_ASYNC, { description: 'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead', }).withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts index b33cfb1512..90c714c557 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity} from '..'; +import {CompilerError} from '..'; import { Identifier, Instruction, @@ -22,6 +22,7 @@ import { ReactiveFunctionVisitor, visitReactiveFunction, } from '../ReactiveScopes/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -108,12 +109,8 @@ class Visitor extends ReactiveFunctionVisitor { isUnmemoized(deps.identifier, this.scopes)) ) { state.push({ - reason: - 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', - description: null, - severity: ErrorSeverity.CannotPreserveMemoization, + errorCode: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null, - suggestions: null, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts index 8989cb1ac2..72e237f660 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..'; +import {CompilerError, EnvironmentConfig} from '..'; import {HIRFunction, IdentifierId} from '../HIR'; import {DEFAULT_GLOBALS} from '../HIR/Globals'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateNoCapitalizedCalls( @@ -33,8 +34,6 @@ export function validateNoCapitalizedCalls( const errors = new CompilerError(); const capitalLoadGlobals = new Map(); const capitalizedProperties = new Map(); - const reason = - 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config'; for (const [, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { switch (value.kind) { @@ -55,11 +54,9 @@ export function validateNoCapitalizedCalls( const calleeIdentifier = value.callee.identifier.id; const calleeName = capitalLoadGlobals.get(calleeIdentifier); if (calleeName != null) { - CompilerError.throwInvalidReact({ - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${calleeName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; @@ -78,12 +75,9 @@ export function validateNoCapitalizedCalls( const propertyIdentifier = value.property.identifier.id; const propertyName = capitalizedProperties.get(propertyIdentifier); if (propertyName != null) { - errors.push({ - severity: ErrorSeverity.InvalidReact, - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${propertyName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts index d026a94ed4..5ec74f0dae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity, SourceLocation} from '..'; +import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, BlockId, @@ -19,6 +19,8 @@ import { eachInstructionValueOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Result} from '../Utils/Result'; /** * Validates that useEffect is not used for derived computations which could/should @@ -43,7 +45,9 @@ import { * const fullName = firstName + ' ' + lastName; * ``` */ -export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { +export function validateNoDerivedComputationsInEffects( + fn: HIRFunction, +): Result { const candidateDependencies: Map = new Map(); const functions: Map = new Map(); const locals: Map = new Map(); @@ -96,9 +100,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { } } } - if (errors.hasErrors()) { - throw errors; - } + return errors.asResult(); } function validateEffect( @@ -218,13 +220,6 @@ function validateEffect( } for (const loc of setStateLocations) { - errors.push({ - reason: - 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', - description: null, - severity: ErrorSeverity.InvalidReact, - loc, - suggestions: null, - }); + errors.pushErrorCode(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, {loc}); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts index 7a79c74780..f829e4b92a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts @@ -5,7 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; +import {ErrorCode} from '../CompilerError'; import { FunctionEffect, HIRFunction, @@ -65,9 +66,7 @@ export function validateNoFreezingKnownMutableFunctions( ? `\`${place.identifier.name.value}\`` : 'a local variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot modify local variables after render completes', + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER, { description: `This argument is a function which may reassign or mutate ${variable} after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts index 85adb79ceb..ee452fc08a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {HIRFunction} from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -35,19 +36,13 @@ export function validateNoImpureFunctionsInRender( ); if (signature != null && signature.impure === true) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail({ kind: 'error', loc: callee.loc, - message: 'Cannot call impure function', + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts index eea6c0a08e..bab00d7159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts @@ -5,8 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {BlockId, HIRFunction} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -35,11 +36,7 @@ export function validateNoJSXInTryStatement( case 'JsxExpression': case 'JsxFragment': { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Avoid constructing JSX within try/catch', - description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.JSX_IN_TRY).withDetail({ kind: 'error', loc: value.loc, message: 'Avoid constructing JSX within try/catch', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts index e1c17625f4..1ffc5d013c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { BlockId, HIRFunction, @@ -26,6 +22,7 @@ import { eachPatternOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Err, Ok, Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -467,11 +464,9 @@ function validateNoRefAccessInRenderImpl( if (fnType.fn.readRefEffect) { didError = true; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.NO_REF_ACCESS_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, message: `This function accesses a ref value`, @@ -730,15 +725,13 @@ function destructure( function guardCheck(errors: CompilerError, operand: Place, env: Env): void { if (env.get(operand.identifier.id)?.kind === 'Guard') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -754,15 +747,13 @@ function validateNoRefValueAccess( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -780,15 +771,13 @@ function validateNoRefPassedToFunction( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Passing a ref to a function may read its value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Passing a ref to a function may read its value during render`, + }, + ), ); } } @@ -802,15 +791,13 @@ function validateNoRefUpdate( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'Ref' || type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Cannot update ref during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Cannot update ref during render`, + }, + ), ); } } @@ -823,21 +810,13 @@ function validateNoDirectRefValueAccess( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: type.loc ?? operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: type.loc ?? operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } - -const ERROR_DESCRIPTION = - 'React refs are values that are not needed for rendering. Refs should only be accessed ' + - 'outside of render, such as in event handlers or effects. ' + - 'Accessing a ref value (the `current` property) during render can cause your component ' + - 'not to update as expected (https://react.dev/reference/react/useRef)'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts index 9c4efa380c..0a5638277d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { HIRFunction, IdentifierId, @@ -20,6 +16,7 @@ import { Place, } from '../HIR'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -95,19 +92,9 @@ export function validateNoSetStateInEffects( const setState = setStateFunctions.get(arg.identifier.id); if (setState !== undefined) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState synchronously within an effect can trigger cascading renders', - description: - 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + - 'In general, the body of an effect should do one or both of the following:\n' + - '* Update external systems with the latest state from React.\n' + - '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + - 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + - '(https://react.dev/learn/you-might-not-need-an-effect)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + ).withDetail({ kind: 'error', loc: setState.loc, message: diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts index 81209d61c6..dc67540ee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts @@ -5,14 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, isSetStateType} from '../HIR'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -127,14 +124,9 @@ function validateNoSetStateInRenderImpl( ) { if (activeManualMemoId !== null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState from useMemo may trigger an infinite loop', - description: - 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_MEMO, + ).withDetail({ kind: 'error', loc: callee.loc, message: 'Found setState() within useMemo()', @@ -142,17 +134,12 @@ function validateNoSetStateInRenderImpl( ); } else if (unconditionalBlocks.has(block.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState during render may trigger an infinite loop', - description: - 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, - message: 'Found setState() within useMemo()', + message: 'Found setState() call here', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index 6bb59247da..7776e15cbb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError, ErrorCode} from '../CompilerError'; import { DeclarationId, Effect, @@ -280,13 +276,8 @@ function validateInferredDep( } } errorState.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - '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. ', + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, { + description: DEBUG || // If the dependency is a named variable then we can report it. Otherwise only print in debug mode (dep.identifier.name != null && dep.identifier.name.kind === 'named') @@ -300,9 +291,6 @@ function validateInferredDep( : 'Inferred dependency not present in source' }.` : '', - ] - .join('') - .trim(), suggestions: null, }).withDetail({ kind: 'error', @@ -534,15 +522,9 @@ class Visitor extends ReactiveFunctionVisitor { !this.prunedScopes.has(identifier.scope.id) ) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', - 'This dependency may be mutated later, which could cause the value to change unexpectedly.', - ].join(''), - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.MANUAL_MEMO_MUTATED_LATER, + ).withDetail({ kind: 'error', loc, message: 'This dependency may be modified later', @@ -582,18 +564,10 @@ class Visitor extends ReactiveFunctionVisitor { for (const identifier of decls) { if (isUnmemoized(identifier, this.scopes)) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. ', - DEBUG - ? `${printIdentifier(identifier)} was not memoized.` - : '', - ] - .join('') - .trim(), + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_REMOVED, { + description: DEBUG + ? `${printIdentifier(identifier)} was not memoized.` + : '', }).withDetail({ kind: 'error', loc, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts index 7f5fb408b4..12722fa2d5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, SourceLocation} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -64,9 +61,7 @@ export function validateStaticComponents( ); if (location != null) { error.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot create components during render', + CompilerDiagnostic.fromCode(ErrorCode.STATIC_COMPONENTS, { description: `Components created during render will reset their state each time they are created. Declare components outside of render. `, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts index 69ab401c89..05531ff211 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateUseMemo(fn: HIRFunction): Result { @@ -73,13 +70,9 @@ export function validateUseMemo(fn: HIRFunction): Result { ? firstParam.loc : firstParam.place.loc; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks may not accept parameters', - description: - 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + ).withDetail({ kind: 'error', loc, message: 'Callbacks with parameters are not supported', @@ -89,14 +82,9 @@ export function validateUseMemo(fn: HIRFunction): Result { if (body.loweredFunc.func.async || body.loweredFunc.func.generator) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'useMemo() callbacks may not be async or generator functions', - description: - 'useMemo() callbacks are called once and must synchronously return a value.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + ).withDetail({ kind: 'error', loc: body.loc, message: 'Async and generator functions are not supported', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md index ce42e65125..e47107723f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-component-tag-function.ts:3:4 1 | function Component() { 2 | const Foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | return ; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md index ee57ea6eb0..5acfcde730 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md @@ -22,13 +22,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-jsx-children.ts:3:4 1 | function Component() { 2 | const foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | // Children are generally access/called during render, so 6 | // modifying a global in a children function is almost diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md index 8476885de7..eb2516e386 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md @@ -18,13 +18,15 @@ function Component() { ``` Found 1 error: -Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.assign-global-in-jsx-spread-attribute.ts:4:4 2 | function Component() { 3 | const foo = () => { > 4 | someGlobal = true; - | ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + | ^^^^^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | return
; 7 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md index 6e522e1666..a5530a1180 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md @@ -20,7 +20,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `$FlowFixMe[react-rule-hook]` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `$FlowFixMe[react-rule-hook]` error.bailout-on-flow-suppression.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index 3221f97731..b348bc6191 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -23,7 +23,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable my-app/react-rule` error.bailout-on-suppression-of-custom-rule.ts:3:0 1 | // @eslintSuppressionRules:["my-app","react-rule"] @@ -36,7 +36,7 @@ error.bailout-on-suppression-of-custom-rule.ts:3:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line my-app/react-rule` error.bailout-on-suppression-of-custom-rule.ts:7:2 5 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md index 6e9887c5ac..d6e0a07d7a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md @@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md index e5c28e6e36..71f96f1ab5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md @@ -19,7 +19,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md index a8a83f6b11..ef6f6091a6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md @@ -17,7 +17,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `callback` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md index 4b49c5f653..96485fb584 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md @@ -17,12 +17,12 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `x` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-assignment-to-global.ts:2:3 1 | function useFoo(props) { > 2 | [x] = props; - | ^ `x` cannot be reassigned + | ^ `x` should not be reassigned 3 | return {x}; 4 | } 5 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md index 6da3b558bd..bae0df94af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-to-local-global-variables.ts:3:6 1 | function Component(props) { 2 | let a; > 3 | [a, b] = props.value; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 4 | 5 | return [a, b]; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md index 8e8b7917d7..f3f2cff6e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md @@ -39,13 +39,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-global-reassignment-indirect.ts:9:4 7 | 8 | const setGlobal = () => { > 9 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 10 | }; 11 | const indirectSetGlobal = () => { 12 | setGlobal(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md index 291d3873b4..8c512ae350 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md @@ -42,13 +42,13 @@ Found 1 error: Error: Cannot access variable before it is declared -`setState` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized error.invalid-hoisting-setstate.ts:19:18 17 | * $2 = Function context=setState 18 | */ > 19 | useEffect(() => setState(2), []); - | ^^^^^^^^ `setState` accessed before it is declared + | ^^^^^^^^ `setState` is accessed before it is declared 20 | 21 | const [state, setState] = useState(0); 22 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md index 3155d64329..cf6b33ce8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md index 4ac7af5bae..768e0300bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md @@ -23,7 +23,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.invalid-mutation-of-possible-props-phi-indirect.ts:4:4 2 | let x = cond ? someGlobal : props.foo; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md index 437fbcb38c..de59216e60 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md @@ -48,7 +48,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md index 25b9a5c186..72bfa49422 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md @@ -15,7 +15,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign a `const` variable +Error: Expect `const` declaration not to be reassigned `x` is declared as const. @@ -23,7 +23,7 @@ error.invalid-reassign-const.ts:3:2 1 | function Component() { 2 | const x = 0; > 3 | x = 1; - | ^ Cannot reassign a `const` variable + | ^ Expect `const` declaration not to be reassigned 4 | } 5 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md index 6379515a05..1b5942449b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md @@ -17,7 +17,7 @@ function useFoo() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md index 368b312022..8a27203097 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md @@ -49,7 +49,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md index 8c7973377d..309fd02906 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md @@ -50,7 +50,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index 3ecbcc97c3..1b4f26b2c2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -43,7 +43,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md index 96be8584be..14d6068b97 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ @@ -32,7 +32,7 @@ error.invalid-sketchy-code-use-forget.ts:1:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:5:2 3 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md index e19cee7532..d77327b90d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md @@ -40,7 +40,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-unclosed-eslint-suppression.ts:2:0 1 | // Note: Everything below this is sketchy diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md index fa9e20a418..f10764dc70 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md @@ -29,7 +29,7 @@ error.invalid-unconditional-set-state-in-render.ts:6:2 4 | const aliased = setX; 5 | > 6 | setX(1); - | ^^^^ Found setState() within useMemo() + | ^^^^ Found setState() call here 7 | aliased(2); 8 | 9 | return x; @@ -42,7 +42,7 @@ error.invalid-unconditional-set-state-in-render.ts:7:2 5 | 6 | setX(1); > 7 | aliased(2); - | ^^^^^^^ Found setState() within useMemo() + | ^^^^^^^ Found setState() call here 8 | 9 | return x; 10 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md index 337b9dd30c..db7b07261e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md @@ -34,7 +34,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `a` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md index 78530caf4a..9903cf2a1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.mutate-property-from-global.ts:4:9 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md index c7b1ac5f45..508aea8d27 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:5:4 3 | function Component(props) { @@ -34,7 +34,7 @@ error.not-useEffect-external-mutate.ts:5:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md index 89bcedf956..606c1c42c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md @@ -24,13 +24,15 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Modifying a variable defined outside a component or hook is not allowed. Consider using an effect +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.object-capture-global-mutation.ts:4:4 2 | function Foo() { 3 | const x = () => { > 4 | window.href = 'foo'; - | ^^^^^^ Modifying a variable defined outside a component or hook is not allowed. Consider using an effect + | ^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | const y = {x}; 7 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md index 2c409ea5b5..cd7f202a1c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md @@ -28,13 +28,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassign-global-fn-arg.ts:5:4 3 | export default function MyApp() { 4 | const fn = () => { > 5 | b = 2; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 6 | }; 7 | return foo(fn); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md index 8835f19ad1..6963940109 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md @@ -21,26 +21,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:4:4 2 | const foo = () => { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | }; 7 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | }; 7 | foo(); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md index 4d259dd8d4..88ae3a7ae8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md @@ -18,26 +18,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:3:2 1 | function Component() { 2 | // Cannot assign to globals > 3 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 4 | moduleLocal = true; 5 | } 6 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | // Cannot assign to globals 3 | someUnknownGlobal = true; > 4 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 5 | } 6 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md index 9c87cafff1..0bcfae2e9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md @@ -24,7 +24,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` error.sketchy-code-exhaustive-deps.ts:6:7 4 | () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md index 7077b733b0..bd1b3ed4c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md @@ -25,7 +25,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.sketchy-code-rules-of-hooks.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md index 7ffe3f84cf..544532b3b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.store-property-in-global.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md index a88d43b352..f4d192530e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md @@ -19,7 +19,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `onClick` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md index 3d54bcd75c..6f9da8fda9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-after-loop-break.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md index c892066bf8..6ab4ecb36e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md @@ -27,7 +27,7 @@ error.unconditional-set-state-in-render-after-loop.ts:6:2 4 | for (const _ of props) { 5 | } > 6 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 7 | return state; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md index a617a2f572..69c81c3ff8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-with-loop-throw.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md index dfb5c7b56f..79369f3608 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md @@ -30,7 +30,7 @@ error.unconditional-set-state-lambda.ts:8:2 6 | setX(1); 7 | }; > 8 | foo(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 9 | 10 | return [x]; 11 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md index f03b514c3f..c8a775499a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md @@ -38,7 +38,7 @@ error.unconditional-set-state-nested-function-expressions.ts:16:2 14 | bar(); 15 | }; > 16 | baz(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 17 | 18 | return [x]; 19 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md index 8432be198b..cfd27c7356 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md @@ -23,13 +23,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `renderCount` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.update-global-should-bailout.ts:3:2 1 | let renderCount = 0; 2 | function useFoo() { > 3 | renderCount += 1; - | ^^^^^^^^^^^^^^^^ `renderCount` cannot be reassigned + | ^^^^^^^^^^^^^^^^ `renderCount` should not be reassigned 4 | return renderCount; 5 | } 6 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md index 188814ee02..1531425c20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md @@ -30,9 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Expected the dependency list for useMemo to be an array literal - -Expected the dependency list for useMemo to be an array literal +Error: Expected the dependency list of useMemo or useCallback to be an array literal error.useMemo-non-literal-depslist.ts:10:4 8 | return text.toUpperCase(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md index d8250c6f57..b4f642df43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Expected a single dynamic gating directive","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md index 08eb396bb1..cf7a8cad85 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr2` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":333},"end":{"line":11,"column":43,"index":357},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":248},"end":{"line":8,"column":46,"index":292},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":277},"end":{"line":8,"column":34,"index":280},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":316},"end":{"line":11,"column":54,"index":368},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":349},"end":{"line":11,"column":42,"index":356},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index b629bfef27..0925a9b982 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -65,7 +65,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index c16890e52e..3e3135929b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -45,7 +45,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md index a9782a3b9e..a6f268555c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index babb4e8969..96b1d866fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -44,7 +44,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md index d78e4becec..bef031723c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md @@ -35,12 +35,12 @@ Found 1 error: Error: Cannot access variable before it is declared -`data` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized 9 | // TDZ violation! 10 | const onRefetch = useCallback(() => { > 11 | refetch(data); - | ^^^^ `data` accessed before it is declared + | ^^^^ `data` is accessed before it is declared 12 | }, [refetch]); 13 | 14 | // The context variable gets frozen here since it's passed to a hook diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md index 80a12e5d40..f1cc5c4808 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md @@ -22,7 +22,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | function Component(props) { @@ -35,7 +35,7 @@ error.not-useEffect-external-mutate.ts:6:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:7:4 5 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md index 41ed513912..bb4d91a4bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md @@ -22,26 +22,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | const foo = () => { 4 | // Cannot assign to globals > 5 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 6 | moduleLocal = true; 7 | }; 8 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:6:4 4 | // Cannot assign to globals 5 | someUnknownGlobal = true; > 6 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 7 | }; 8 | foo(); 9 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md index 6089255fd5..84339b0759 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md @@ -19,26 +19,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | function Component() { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | } 7 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:5:2 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | } 7 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md index 2e0c890367..8441e79925 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr2` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":365},"end":{"line":11,"column":43,"index":389},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":280},"end":{"line":8,"column":46,"index":324},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":309},"end":{"line":8,"column":34,"index":312},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":348},"end":{"line":11,"column":54,"index":400},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":381},"end":{"line":11,"column":42,"index":388},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md index 27af59e175..f433f8cb88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md @@ -24,8 +24,6 @@ Found 1 error: Error: Expected the first argument to be an inline function expression -Expected the first argument to be an inline function expression - error.validate-useMemo-named-function.ts:9:20 7 | // for now. 8 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index 006d2a49c0..bda9c3b694 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -31,7 +31,7 @@ function Component({prop1}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:4) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index 8481ed2c57..9604f69476 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -15,7 +15,7 @@ console.log(fire == null); ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index f84686bc36..02753ce345 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -32,7 +32,7 @@ function Component({props, bar}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md index 81c36a362c..cdb0726f2e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md @@ -26,7 +26,7 @@ function Component({props, bar}) { ``` Found 2 errors: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. @@ -39,7 +39,7 @@ error.invalid-outside-effect.ts:8:2 10 | useCallback(() => { 11 | fire(foo(props)); -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md index 96cea9c08f..d82f1ee1b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md @@ -27,7 +27,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md index 4dc5336ebe..a841813630 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md @@ -30,7 +30,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts new file mode 100644 index 0000000000..c3d6704504 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts @@ -0,0 +1,32 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoImpureFunctionCallsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no impure function calls rule', NoImpureFunctionCallsRule, { + valid: [], + invalid: [ + { + name: 'Known impure function calls are caught', + code: normalizeIndent` + function Component() { + const date = Date.now(); + const now = performance.now(); + const rand = Math.random(); + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts new file mode 100644 index 0000000000..9192087b31 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts @@ -0,0 +1,85 @@ +/** + * 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 {RulesOfHooksRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('rules-of-hooks', RulesOfHooksRule, { + valid: [ + { + name: 'Basic example', + code: normalizeIndent` + function Component() { + useHook(); + return
Hello world
; + } + `, + }, + { + name: 'Violation with Flow suppression', + code: ` + // Valid since error already suppressed with flow. + function useHook() { + if (cond) { + // $FlowFixMe[react-rule-hook] + useConditionalHook(); + } + } + `, + }, + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function useConditional() { + cond ?? useConditionalHook(); + props.cond && useConditionalHook(); + return
Hello world
; + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts new file mode 100644 index 0000000000..8463e860df --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts @@ -0,0 +1,31 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoAmbiguousJsxRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ambiguous JSX rule', NoAmbiguousJsxRule, { + valid: [], + invalid: [ + { + name: 'JSX in try blocks are warned against', + code: normalizeIndent` + function Component(props) { + let el; + try { + el = ; + } catch { + return null; + } + return el; + } + `, + errors: [makeTestCaseError(ErrorCode.JSX_IN_TRY)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts new file mode 100644 index 0000000000..821cab8007 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {NoCapitalizedCallsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidCapitalizedCallsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('no-capitalized-calls', NoCapitalizedCallsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + import Child from './Child'; + function Component() { + return <> + {Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Method call violation', + code: normalizeIndent` + import myModule from './MyModule'; + function Component() { + return <> + {myModule.Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + import Child1 from './Child1'; + import MyModule from './MyModule'; + function Component() { + return <> + {Child1()} + {MyModule.Child2()} + ; + }`, + errors: [ + invalidCapitalizedCallsRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts new file mode 100644 index 0000000000..a8498d303f --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts @@ -0,0 +1,27 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoRefAccessInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ref access in render rule', NoRefAccessInRenderRule, { + valid: [], + invalid: [ + { + name: 'validate against simple ref access in render', + code: normalizeIndent` + function Component(props) { + const ref = useRef(null); + const value = ref.current; + return value; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_REF_ACCESS_IN_RENDER)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts new file mode 100644 index 0000000000..43ec6ad010 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts @@ -0,0 +1,48 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoSetStateInEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in effects rule', NoSetStateInEffectsRule, { + valid: [], + invalid: [ + { + name: 'unconditional setState in useEffect', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + setState(s => s + 1); + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + { + name: 'conditional setState in render', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + if (state % 2 === 0) { + setState(state + 1); + } + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts new file mode 100644 index 0000000000..77ce895339 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {ValidateSetStateInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in render rule', ValidateSetStateInRenderRule, { + valid: [], + invalid: [ + { + name: 'setState in useMemo', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + return item; + }, [cond, item, init]); + + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'unconditional setState in render', + code: normalizeIndent` + function Component(props) { + const [x, setX] = useState(0); + const aliased = setX; + + setX(1); + aliased(2); + + return x; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts new file mode 100644 index 0000000000..77f6dd93fb --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {NoUnusedDirectivesRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule} from './shared-utils'; + +testRule('no unused directives rule', NoUnusedDirectivesRule, { + valid: [], + invalid: [ + { + name: "Unused 'use no forget' directive is reported when no errors are present on components", + code: normalizeIndent` + function Component() { + 'use no forget'; + return
Hello world
+ } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction Component() {\n \n return
Hello world
\n}\n', + }, + ], + }, + ], + }, + + { + name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", + code: normalizeIndent` + function notacomponent() { + 'use no forget'; + return 1 + 1; + } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', + }, + ], + }, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts new file mode 100644 index 0000000000..c0e0cf07c9 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts @@ -0,0 +1,172 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + testRule, + makeTestCaseError, + TestRecommendedRules, +} from './shared-utils'; +import {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; + +testRule('plugin-recommended', TestRecommendedRules, { + valid: [ + { + name: 'Basic example with component syntax', + code: normalizeIndent` + export default component HelloWorld( + text: string = 'Hello!', + onClick: () => void, + ) { + return
{text}
; + } + `, + }, + + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Multiple diagnostic kinds from the same function are surfaced', + code: normalizeIndent` + import Child from './Child'; + function Component() { + const result = cond ?? useConditionalHook(); + return <> + {Child(result)} + ; + } + `, + errors: [ + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + { + name: 'Multiple diagnostics within the same file are surfaced', + code: normalizeIndent` + function useConditional1() { + 'use memo'; + return cond ?? useConditionalHook(); + } + function useConditional2(props) { + 'use memo'; + return props.cond && useConditionalHook(); + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + { + name: "'use no forget' does not disable eslint rule", + code: normalizeIndent` + let count = 0; + function Component() { + 'use no forget'; + return cond ?? useConditionalHook(); + + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple non-fatal useMemo diagnostics are surfaced', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + }, [cond, item, init]); + + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'Pipeline errors are reported', + code: normalizeIndent` + import useMyEffect from 'useMyEffect'; + import {AUTODEPS} from 'react'; + function Component({a}) { + 'use no memo'; + useMyEffect(() => console.log(a.b), AUTODEPS); + return
Hello world
; + } + `, + options: [ + { + environment: { + inferEffectDependencies: [ + { + function: { + source: 'useMyEffect', + importSpecifierName: 'default', + }, + autodepsIndex: 1, + }, + ], + }, + }, + ], + errors: [ + { + message: /Cannot infer dependencies of this effect/, + }, + ], + }, + ], +}); + +testRule('rules that are not enabled do not error', StaticComponentsRule, { + valid: [ + { + name: 'simple case', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + }, + ], + invalid: [], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts deleted file mode 100644 index bff40e9649..0000000000 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts +++ /dev/null @@ -1,287 +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 {ErrorSeverity} from 'babel-plugin-react-compiler/src'; -import {RuleTester as ESLintTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)![0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: ESLintTester.ValidTestCase[]; - invalid: ESLintTester.InvalidTestCase[]; -}; - -const tests: CompilerTestCases = { - valid: [ - { - name: 'Basic example', - code: normalizeIndent` - function foo(x, y) { - if (x) { - return foo(false, y); - } - return [y * 10]; - } - `, - }, - { - name: 'Violation with Flow suppression', - code: ` - // Valid since error already suppressed with flow. - function useHookWithHook() { - if (cond) { - // $FlowFixMe[react-rule-hook] - useConditionalHook(); - } - } - `, - }, - { - name: 'Basic example with component syntax', - code: normalizeIndent` - export default component HelloWorld( - text: string = 'Hello!', - onClick: () => void, - ) { - return
{text}
; - } - `, - }, - { - name: 'Unsupported syntax', - code: normalizeIndent` - function foo(x) { - var y = 1; - return y * x; - } - `, - }, - { - // OK because invariants are only meant for the compiler team's consumption - name: '[Invariant] Defined after use', - code: normalizeIndent` - function Component(props) { - let y = function () { - m(x); - }; - - let x = { a }; - m(x); - return y; - } - `, - }, - { - name: "Classes don't throw", - code: normalizeIndent` - class Foo { - #bar() {} - } - `, - }, - ], - invalid: [ - { - name: 'Reportable levels can be configured', - options: [{reportableLevels: new Set([ErrorSeverity.Todo])}], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: '[InvalidReact] ESlint suppression', - // Indentation is intentionally weird so it doesn't add extra whitespace - code: normalizeIndent` - function Component(props) { - // eslint-disable-next-line react-hooks/rules-of-hooks - return
{props.foo}
; - }`, - errors: [ - { - message: /React Compiler has skipped optimizing this component/, - suggestions: [ - { - output: normalizeIndent` - function Component(props) { - - return
{props.foo}
; - }`, - }, - ], - }, - { - message: - "Definition for rule 'react-hooks/rules-of-hooks' was not found.", - }, - ], - }, - { - name: 'Multiple diagnostics are surfaced', - options: [ - { - reportableLevels: new Set([ - ErrorSeverity.Todo, - ErrorSeverity.InvalidReact, - ]), - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - } - function Bar(props) { - props.a.b = 2; - return
{props.c}
- }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - { - message: /Modifying component props or hook arguments is not allowed/, - }, - ], - }, - { - name: 'Test experimental/unstable report all bailouts mode', - options: [ - { - reportableLevels: new Set([ErrorSeverity.InvalidReact]), - __unstable_donotuse_reportAllBailouts: true, - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: "'use no forget' does not disable eslint rule", - code: normalizeIndent` - let count = 0; - function Component() { - 'use no forget'; - count = count + 1; - return
Hello world {count}
- } - `, - errors: [ - { - message: - /Cannot reassign variables declared outside of the component\/hook/, - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on components", - code: normalizeIndent` - function Component() { - 'use no forget'; - return
Hello world
- } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction Component() {\n \n return
Hello world
\n}\n', - }, - ], - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", - code: normalizeIndent` - function notacomponent() { - 'use no forget'; - return 1 + 1; - } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', - }, - ], - }, - ], - }, - { - name: 'Pipeline errors are reported', - code: normalizeIndent` - import useMyEffect from 'useMyEffect'; - import {AUTODEPS} from 'react'; - function Component({a}) { - 'use no memo'; - useMyEffect(() => console.log(a.b), AUTODEPS); - return
Hello world
; - } - `, - options: [ - { - environment: { - inferEffectDependencies: [ - { - function: { - source: 'useMyEffect', - importSpecifierName: 'default', - }, - autodepsIndex: 1, - }, - ], - }, - }, - ], - errors: [ - { - message: /Cannot infer dependencies of this effect/, - }, - ], - }, - ], -}; - -const eslintTester = new ESLintTester({ - parser: require.resolve('hermes-eslint'), - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, -}); -eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts index 5a2bea6852..87baf724e1 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts @@ -6,22 +6,11 @@ */ import {RuleTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)[0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: RuleTester.ValidTestCase[]; - invalid: RuleTester.InvalidTestCase[]; -}; +import { + CompilerTestCases, + normalizeIndent, + TestRecommendedRules, +} from './shared-utils'; const tests: CompilerTestCases = { valid: [ @@ -70,6 +59,7 @@ const tests: CompilerTestCases = { }; const eslintTester = new RuleTester({ + // @ts-ignore[2353] - outdated types parser: require.resolve('@typescript-eslint/parser'), }); -eslintTester.run('react-compiler', ReactCompilerRule, tests); +eslintTester.run('react-compiler', TestRecommendedRules, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts new file mode 100644 index 0000000000..b2f4b54994 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts @@ -0,0 +1,44 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidStaticComponentsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('static-components', StaticComponentsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function Example(props) { + const Component = new ComponentFactory(); + return ; + } + `, + errors: [invalidStaticComponentsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function Example(props) { + const Component1 = new ComponentFactory(); + const Component2 = new ComponentFactory(); + return <> + + + ; + }`, + errors: [ + invalidStaticComponentsRuleErrorMessage, + invalidStaticComponentsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts new file mode 100644 index 0000000000..8d7f481f15 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts @@ -0,0 +1,36 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {UnnecessaryEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('unnecessary effects rule', UnnecessaryEffectsRule, { + valid: [], + invalid: [ + { + name: 'test case from React docs', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + // https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state + function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + + // 🔴 Avoid: redundant state and unnecessary Effect + const [fullName, setFullName] = useState(''); + useEffect(() => { + setFullName(capitalize(firstName + ' ' + lastName)); + }, [firstName, lastName]); + + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts new file mode 100644 index 0000000000..85c937a2de --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts @@ -0,0 +1,43 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {UseMemoRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('use memo rule', UseMemoRule, { + valid: [], + invalid: [ + { + name: 'Simple async violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component({a, b}) { + let x = useMemo(async () => { + await a; + }, []); + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC)], + }, + { + name: 'Simple parameters violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component() { + let x = useMemo(c => a, []); + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts new file mode 100644 index 0000000000..f9553730e4 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts @@ -0,0 +1,98 @@ +import {RuleTester as ESLintTester, Rule} from 'eslint'; +import { + ErrorCode, + ErrorCodeDetails, +} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import escape from 'regexp.escape'; +import {configs} from '../src/index'; + +/** + * A string template tag that removes padding from the left side of multi-line strings + * @param {Array} strings array of code strings (only one expected) + */ +export function normalizeIndent(strings: TemplateStringsArray): string { + const codeLines = strings[0].split('\n'); + const leftPadding = codeLines[1].match(/\s+/)![0]; + return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); +} + +export type CompilerTestCases = { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; +}; + +export const invalidHookRuleErrorMessage: ESLintTester.TestCaseError = { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason), + ), +}; + +export const invalidCapitalizedCallsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.CAPITALIZED_CALLS].reason), + ), + }; + +export const invalidStaticComponentsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.STATIC_COMPONENTS].reason), + ), + }; + +export function makeTestCaseError(code: ErrorCode): ESLintTester.TestCaseError { + return { + message: new RegExp(escape(ErrorCodeDetails[code].reason)), + }; +} + +export function testRule( + name: string, + rule: Rule.RuleModule, + tests: { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; + }, +): void { + const eslintTester = new ESLintTester({ + // @ts-ignore[2353] - outdated types + parser: require.resolve('hermes-eslint'), + parserOptions: { + ecmaVersion: 2015, + sourceType: 'module', + enableExperimentalComponentSyntax: true, + }, + }); + + eslintTester.run(name, rule, tests); +} + +/** + * Aggregates all recommended rules from the plugin. + */ +export const TestRecommendedRules: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Disallow capitalized function calls', + category: 'Possible Errors', + recommended: true, + }, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context) { + for (const rule of Object.values( + configs.recommended.plugins['react-compiler'].rules, + )) { + const listener = rule.create(context); + if (Object.entries(listener).length !== 0) { + throw new Error('TODO: handle rules that return listeners to eslint'); + } + } + return {}; + }, +}; + +test('no test', () => {}); diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index 2dd191f033..e5402611e2 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -24,11 +24,13 @@ "@babel/preset-typescript": "^7.18.6", "@babel/types": "^7.26.0", "@types/eslint": "^8.56.12", + "@types/jest": "^30.0.0", "@types/node": "^20.2.5", "babel-jest": "^29.0.3", "eslint": "8.57.0", "hermes-eslint": "^0.25.1", - "jest": "^29.5.0" + "jest": "^29.5.0", + "regexp.escape": "^2.0.1" }, "engines": { "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" diff --git a/compiler/packages/eslint-plugin-react-compiler/src/index.ts b/compiler/packages/eslint-plugin-react-compiler/src/index.ts index a3577a101e..681e9844ef 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/index.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/index.ts @@ -5,29 +5,61 @@ * LICENSE file in the root directory of this source tree. */ -import ReactCompilerRule from './rules/ReactCompilerRule'; +import * as rules from './rules/ReactCompilerRule'; const meta = { name: 'eslint-plugin-react-compiler', }; -const rules = { - 'react-compiler': ReactCompilerRule, +/** + * Validates React components and hooks follow rules of React + * and follow best practices + */ +const validationRules = { + 'rules-of-hooks': rules.RulesOfHooksRule, + 'no-capitalized-calls': rules.NoCapitalizedCallsRule, + 'no-unstable-components': rules.StaticComponentsRule, + 'validate-use-memo-callbacks': rules.UseMemoRule, + 'validate-writes': rules.InvalidWritesRule, + 'no-unsafe-refs': rules.UnsafeRefsRule, + 'validate-set-state-in-render': rules.ValidateSetStateInRenderRule, + 'no-set-state-in-effects': rules.NoSetStateInEffectsRule, + 'no-ref-access-in-render': rules.NoRefAccessInRenderRule, + 'no-impure-function-calls': rules.NoImpureFunctionCallsRule, + 'unnecessary-effects': rules.UnnecessaryEffectsRule, + 'no-ambiguous-jsx': rules.NoAmbiguousJsxRule, +}; + +const recommendedRules = { + ...validationRules, + 'no-unsupported-syntax': rules.NoUnsupportedSyntaxRule, + + /** Validation for React Compiler inline configuration */ + 'no-unused-directives': rules.NoUnusedDirectivesRule, + 'validate-compiler-config': rules.ValidateCompilerConfigRule, +}; + +const allRules = { + ...recommendedRules, + /** Warn on syntax that React Compiler cannot transform */ + 'no-todo-syntax': rules.WarnOnTodoSyntaxRule, + 'warn-on-compilation-failures': rules.WarnOnUnactionableFailuresRule, }; const configs = { recommended: { plugins: { 'react-compiler': { - rules: { - 'react-compiler': ReactCompilerRule, - }, + rules: recommendedRules, }, }, - rules: { - 'react-compiler/react-compiler': 'error' as const, - }, + rules: Object.fromEntries( + Object.keys(recommendedRules).map(ruleName => [ + 'react-compiler/' + ruleName, + 'error', + ]), + ) as Record, }, }; -export {configs, rules, meta}; +export {configs, allRules as rules, meta}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index 730b6ff6f8..0058464090 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -5,43 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import {transformFromAstSync} from '@babel/core'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import type {SourceLocation as BabelSourceLocation} from '@babel/types'; -import BabelPluginReactCompiler, { - CompilerDiagnostic, +import { CompilerDiagnosticOptions, - CompilerErrorDetail, CompilerErrorDetailOptions, CompilerSuggestionOperation, - ErrorSeverity, - parsePluginOptions, - validateEnvironmentConfig, - OPT_OUT_DIRECTIVES, - type PluginOptions, } from 'babel-plugin-react-compiler/src'; -import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Rule} from 'eslint'; -import {Statement} from 'estree'; -import * as HermesParser from 'hermes-parser'; +import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler'; +import {LinterCategory} from 'babel-plugin-react-compiler/src/CompilerError'; function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } -const DEFAULT_REPORTABLE_LEVELS = new Set([ - ErrorSeverity.InvalidReact, - ErrorSeverity.InvalidJS, -]); -let reportableLevels = DEFAULT_REPORTABLE_LEVELS; - -function isReportableDiagnostic( - detail: CompilerErrorDetail | CompilerDiagnostic, -): boolean { - return reportableLevels.has(detail.severity); -} - function makeSuggestions( detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array { @@ -95,28 +72,97 @@ function makeSuggestions( return suggest; } -const COMPILER_OPTIONS: Partial = { - noEmit: true, - panicThreshold: 'none', - // Don't emit errors on Flow suppressions--Flow already gave a signal - flowSuppressions: false, - environment: validateEnvironmentConfig({ - validateRefAccessDuringRender: true, - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - }), -}; +function getReactCompilerResult(context: Rule.RuleContext): RunCacheEntry { + // Compat with older versions of eslint + const sourceCode = context.sourceCode ?? context.getSourceCode(); + const filename = context.filename ?? context.getFilename(); + const userOpts = context.options[0] ?? {}; -const rule: Rule.RuleModule = { + const results = runReactCompiler({ + sourceCode, + filename, + userOpts, + }); + + return results; +} + +function hasFlowSuppression( + program: RunCacheEntry, + nodeLoc: BabelSourceLocation, + suppressions: Array, +): boolean { + for (const commentNode of program.flowSuppressions) { + if ( + suppressions.includes(commentNode.code) && + commentNode.line === nodeLoc.start.line - 1 + ) { + return true; + } + } + return false; +} + +function makeRule(filter: Array): Rule.RuleModule['create'] { + return (context: Rule.RuleContext): Rule.RuleListener => { + const result = getReactCompilerResult(context); + + for (const event of result.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if ( + detail.linterCategory != null && + filter.includes(detail.linterCategory) + ) { + const loc = detail.primaryLocation(); + if (loc == null || typeof loc === 'symbol') { + continue; + } + if ( + hasFlowSuppression(result, loc, [ + 'react-rule-hook', + 'react-rule-unsafe-ref', + ]) + ) { + // If Flow already caught this error, we don't need to report it again. + continue; + } + // TODO: if multiple rules report the same linter category, + // we should deduplicate them with a "reported" set + context.report({ + message: detail.printErrorMessage(result.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); + } + } + } + return {}; + }; +} + +const unactionableBailouts: Rule.RuleModule = { meta: { type: 'problem', docs: { - description: 'Surfaces diagnostics from React Forget', + description: 'Surfaces unactionable compilation bailouts', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + }, + create(context: Rule.RuleContext): Rule.RuleListener { + return {}; + }, +}; + +export const RulesOfHooksRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to the rules of hooks', recommended: true, }, fixable: 'code', @@ -124,234 +170,284 @@ const rule: Rule.RuleModule = { // validation is done at runtime with zod schema: [{type: 'object', additionalProperties: true}], }, - create(context: Rule.RuleContext) { - // Compat with older versions of eslint - const sourceCode = context.sourceCode ?? context.getSourceCode(); - const filename = context.filename ?? context.getFilename(); - const userOpts = context.options[0] ?? {}; - if ( - userOpts.reportableLevels != null && - userOpts.reportableLevels instanceof Set - ) { - reportableLevels = userOpts.reportableLevels; - } else { - reportableLevels = DEFAULT_REPORTABLE_LEVELS; - } - /** - * Experimental setting to report all compilation bailouts on the compilation - * unit (e.g. function or hook) instead of the offensive line. - * Intended to be used when a codebase is 100% reliant on the compiler for - * memoization (i.e. deleted all manual memo) and needs compilation success - * signals for perf debugging. - */ - let __unstable_donotuse_reportAllBailouts: boolean = false; - if ( - userOpts.__unstable_donotuse_reportAllBailouts != null && - typeof userOpts.__unstable_donotuse_reportAllBailouts === 'boolean' - ) { - __unstable_donotuse_reportAllBailouts = - userOpts.__unstable_donotuse_reportAllBailouts; - } + create: makeRule([LinterCategory.RULES_OF_HOOKS]), +}; - let shouldReportUnusedOptOutDirective = true; - const options: PluginOptions = parsePluginOptions({ - ...COMPILER_OPTIONS, - ...userOpts, - environment: { - ...COMPILER_OPTIONS.environment, - ...userOpts.environment, - }, - }); - const userLogger: Logger | null = options.logger; - options.logger = { - logEvent: (eventFilename, event): void => { - userLogger?.logEvent(eventFilename, event); - if (event.kind === 'CompileError') { - shouldReportUnusedOptOutDirective = false; - const detail = event.detail; - const suggest = makeSuggestions(detail.options); - if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) { - const loc = detail.primaryLocation(); - const locStr = - loc != null && typeof loc !== 'symbol' - ? ` (@:${loc.start.line}:${loc.start.column})` - : ''; - /** - * Report bailouts with a smaller span (just the first line). - * Compiler bailout lints only serve to flag that a react function - * has not been optimized by the compiler for codebases which depend - * on compiler memo heavily for perf. These lints are also often not - * actionable. - */ - let endLoc; - if (event.fnLoc.end.line === event.fnLoc.start.line) { - endLoc = event.fnLoc.end; - } else { - endLoc = { - line: event.fnLoc.start.line, - // Babel loc line numbers are 1-indexed - column: - sourceCode.text.split(/\r?\n|\r|\n/g)[ - event.fnLoc.start.line - 1 - ]?.length ?? 0, - }; - } - const firstLineLoc = { - start: event.fnLoc.start, - end: endLoc, - }; - context.report({ - message: `${detail.printErrorMessage(sourceCode.text, {eslint: true})} ${locStr}`, - loc: firstLineLoc, - suggest, - }); - } +export const NoCapitalizedCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to capitalized calls', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.CAPITALIZED_CALLS]), +}; +export const StaticComponentsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'todo', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.STATIC_COMPONENTS]), +}; + +export const InvalidWritesRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.INVALID_WRITE]), +}; +export const UseMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Surfaces compilation errors related to invalid useMemo usage', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.VALIDATE_MANUAL_MEMO]), +}; + +// TODO: test cases +export const NoDynamicManualMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.DYNAMIC_MANUAL_MEMO]), +}; + +export const UnsafeRefsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to unsafe refs', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.EXHAUSTIVE_DEPS]), +}; + +export const ValidateSetStateInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_RENDER]), +}; + +export const NoSetStateInEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to setState in render', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_EFFECTS]), +}; + +export const NoRefAccessInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_REF_ACCESS_IN_RENDER]), +}; + +export const NoImpureFunctionCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.IMPURE_FUNCTIONS]), +}; + +export const UnnecessaryEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNNECESSARY_EFFECTS]), +}; + +export const NoAmbiguousJsxRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on JSX usage that is ambiguous.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.JSX_IN_TRY]), +}; + +// TODO: test cases +export const NoUnsupportedSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler does not and will not support.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNSUPPORTED_SYNTAX]), +}; + +// TODO: test cases +export const WarnOnTodoSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler currently does not support, but may in the future.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.TODO_SYNTAX]), +}; + +// TODO: test cases +export const ValidateCompilerConfigRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Validates the React Compiler configuration', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.COMPILER_CONFIG]), +}; + +export const WarnOnUnactionableFailuresRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on compilation failures that are not actionable', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const event of results.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if (detail.linterCategory == null) { const loc = detail.primaryLocation(); - if ( - !isReportableDiagnostic(detail) || - loc == null || - typeof loc === 'symbol' - ) { - return; + if (loc == null || typeof loc === 'symbol') { + continue; } - if ( - hasFlowSuppression(loc, 'react-rule-hook') || - hasFlowSuppression(loc, 'react-rule-unsafe-ref') - ) { - // If Flow already caught this error, we don't need to report it again. - return; - } - if (loc != null) { - context.report({ - message: detail.printErrorMessage(sourceCode.text, { - eslint: true, - }), - loc, - suggest, - }); - } - } - }, - }; - - try { - options.environment = validateEnvironmentConfig( - options.environment ?? {}, - ); - } catch (err: unknown) { - options.logger?.logEvent('', err as LoggerEvent); - } - - function hasFlowSuppression( - nodeLoc: BabelSourceLocation, - suppression: string, - ): boolean { - const comments = sourceCode.getAllComments(); - const flowSuppressionRegex = new RegExp( - '\\$FlowFixMe\\[' + suppression + '\\]', - ); - for (const commentNode of comments) { - if ( - flowSuppressionRegex.test(commentNode.value) && - commentNode.loc!.end.line === nodeLoc.start.line - 1 - ) { - return true; + context.report({ + message: detail.printErrorMessage(results.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); } } - return false; - } - - let babelAST; - if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { - try { - const {parse: babelParse} = require('@babel/parser'); - babelAST = babelParse(sourceCode.text, { - filename, - sourceType: 'unambiguous', - plugins: ['typescript', 'jsx'], - }); - } catch { - /* empty */ - } - } else { - try { - babelAST = HermesParser.parse(sourceCode.text, { - babel: true, - enableExperimentalComponentSyntax: true, - sourceFilename: filename, - sourceType: 'module', - }); - } catch { - /* empty */ - } - } - - if (babelAST != null) { - try { - transformFromAstSync(babelAST, sourceCode.text, { - filename, - highlightCode: false, - retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], - sourceType: 'module', - configFile: false, - babelrc: false, - }); - } catch (err) { - /* errors handled by injected logger */ - } - } - - function reportUnusedOptOutDirective(stmt: Statement) { - if ( - stmt.type === 'ExpressionStatement' && - stmt.expression.type === 'Literal' && - typeof stmt.expression.value === 'string' && - OPT_OUT_DIRECTIVES.has(stmt.expression.value) && - stmt.loc != null - ) { - context.report({ - message: `Unused '${stmt.expression.value}' directive`, - loc: stmt.loc, - suggest: [ - { - desc: 'Remove the directive', - fix(fixer) { - return fixer.remove(stmt); - }, - }, - ], - }); - } - } - if (shouldReportUnusedOptOutDirective) { - return { - FunctionDeclaration(fnDecl) { - for (const stmt of fnDecl.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - ArrowFunctionExpression(fnExpr) { - if (fnExpr.body.type === 'BlockStatement') { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - } - }, - FunctionExpression(fnExpr) { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - }; - } else { - return {}; } + return {}; }, }; -export default rule; +export const NoUnusedDirectivesRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const directive of results.unusedOptOutDirectives) { + context.report({ + message: `Unused '${directive.directive}' directive`, + loc: directive.loc, + suggest: [ + { + desc: 'Remove the directive', + fix(fixer) { + return fixer.removeRange(directive.range); + }, + }, + ], + }); + } + return {}; + }, +}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts new file mode 100644 index 0000000000..655eaf5197 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -0,0 +1,323 @@ +/** + * 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 {transformFromAstSync, traverse} from '@babel/core'; +import {parse as babelParse} from '@babel/parser'; +import {Directive, File} from '@babel/types'; +// @ts-expect-error: no types available +import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; +import BabelPluginReactCompiler, { + parsePluginOptions, + validateEnvironmentConfig, + OPT_OUT_DIRECTIVES, + type PluginOptions, +} from 'babel-plugin-react-compiler/src'; +import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; +import type {SourceCode} from 'eslint'; +import {SourceLocation} from 'estree'; +// @ts-expect-error: no types available +import * as HermesParser from 'hermes-parser'; +import {isDeepStrictEqual} from 'util'; +import type {ParseResult} from '@babel/parser'; + +const COMPILER_OPTIONS: Partial = { + noEmit: true, + panicThreshold: 'none', + // Don't emit errors on Flow suppressions--Flow already gave a signal + flowSuppressions: false, + environment: validateEnvironmentConfig({ + validateRefAccessDuringRender: true, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + // TODO: remove, this should be in the type system + validateNoCapitalizedCalls: [], + validateHooksUsage: true, + validateNoDerivedComputationsInEffects: true, + }), +}; + +export type UnusedOptOutDirective = { + loc: SourceLocation; + range: [number, number]; + directive: string; +}; +export type RunCacheEntry = { + sourceCode: string; + filename: string; + userOpts: PluginOptions; + flowSuppressions: Array<{line: number; code: string}>; + unusedOptOutDirectives: Array; + events: LoggerEvent[]; +}; + +type RunParams = { + sourceCode: SourceCode; + filename: string; + userOpts: PluginOptions; +}; +const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g; + +function getFlowSuppressions( + sourceCode: SourceCode, +): Array<{line: number; code: string}> { + const comments = sourceCode.getAllComments(); + const results: Array<{line: number; code: string}> = []; + + for (const commentNode of comments) { + const matches = commentNode.value.matchAll(FLOW_SUPPRESSION_REGEX); + for (const match of matches) { + if (match.index != null && commentNode.loc != null) { + const code = match[1]; + results.push({ + line: commentNode.loc!.end.line, + code, + }); + } + } + } + return results; +} + +function filterUnusedOptOutDirectives( + directives: ReadonlyArray, +): Array { + const results: Array = []; + for (const directive of directives) { + if ( + OPT_OUT_DIRECTIVES.has(directive.value.value) && + directive.loc != null + ) { + results.push({ + loc: directive.loc, + directive: directive.value.value, + range: [directive.start!, directive.end!], + }); + } + } + return results; +} + +function runReactCompilerImpl({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + // Compat with older versions of eslint + for (const [key, entry] of Object.entries(userOpts)) { + if (key === 'environment' && COMPILER_OPTIONS.environment != null) { + for (const envKey of Object.keys(entry as Record)) { + if ( + COMPILER_OPTIONS.environment.hasOwnProperty(envKey) && + isDeepStrictEqual( + (entry as Record)[envKey], + (COMPILER_OPTIONS.environment as Record)[envKey], + ) + ) { + console.warn('Conflicting environment option detected: ' + envKey); + } + } + } else if (COMPILER_OPTIONS.hasOwnProperty(key)) { + if (isDeepStrictEqual(entry, (COMPILER_OPTIONS as any)[key])) { + console.warn('Conflicting option detected: ' + key); + } + } + } + const options: PluginOptions = parsePluginOptions({ + ...COMPILER_OPTIONS, + ...userOpts, + environment: { + ...COMPILER_OPTIONS.environment, + ...userOpts.environment, + }, + }); + const results: RunCacheEntry = { + sourceCode: sourceCode.text, + filename, + userOpts, + flowSuppressions: [], + unusedOptOutDirectives: [], + events: [], + }; + const userLogger: Logger | null = options.logger; + options.logger = { + logEvent: (eventFilename, event): void => { + userLogger?.logEvent(eventFilename, event); + results.events.push(event); + }, + }; + + try { + options.environment = validateEnvironmentConfig(options.environment ?? {}); + } catch (err: unknown) { + options.logger?.logEvent(filename, err as LoggerEvent); + } + + let babelAST: ParseResult | null = null; + if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { + try { + babelAST = babelParse(sourceCode.text, { + sourceFilename: filename, + sourceType: 'unambiguous', + plugins: ['typescript', 'jsx'], + }); + } catch { + /* empty */ + } + } else { + try { + babelAST = HermesParser.parse(sourceCode.text, { + babel: true, + enableExperimentalComponentSyntax: true, + sourceFilename: filename, + sourceType: 'module', + }); + } catch { + /* empty */ + } + } + + if (babelAST != null) { + results.flowSuppressions = getFlowSuppressions(sourceCode); + try { + transformFromAstSync(babelAST, sourceCode.text, { + filename, + highlightCode: false, + retainLines: true, + plugins: [ + [PluginProposalPrivateMethods, {loose: true}], + [BabelPluginReactCompiler, options], + ], + sourceType: 'module', + configFile: false, + babelrc: false, + }); + + if (results.events.filter(e => e.kind === 'CompileError').length === 0) { + traverse(babelAST, { + FunctionDeclaration(path) { + path.node; + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + ArrowFunctionExpression(path) { + if (path.node.body.type === 'BlockStatement') { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + } + }, + FunctionExpression(path) { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + }); + } + } catch (err) { + /* errors handled by injected logger */ + } + } + + return results; +} + +const SENTINEL = Symbol(); + +type T = {[k: string]: any}; + +// Array backed LRU cache -- should be small < 10 elements +class LRUCache { + // newest at headIdx, then headIdx + 1, ..., tailIdx + #values: Array<[K, T | Error] | [typeof SENTINEL, void]>; + #headIdx: number = 0; + // #store: (key: K) => T | Error; + // #isValue: null | ((key: T | Error) => key is T); + + constructor( + size: number, + // store: (key: K) => T | Error, + // isValue?: (key: T | Error) => key is T, + ) { + this.#values = new Array(size).fill(SENTINEL); + // this.#store = store; + // this.#isValue = isValue ?? null; + } + + // gets a value and sets it as "recently used" + get(key: K): T | null { + let idx = this.#values.findIndex(entry => entry[0] === key); + // If found, move to front + if (idx === this.#headIdx) { + return this.#values[this.#headIdx][1] as T; + } else if (idx < 0) { + return null; + // const value = this.#store(key); + // if (this.#isValue && !this.#isValue(value)) { + // return value; // Return error directly + // } + // this.#headIdx = + // (this.#headIdx - 1 + this.#values.length) % this.#values.length; + // this.#values[this.#headIdx] = [key, value]; + } + + const entry: [K, T] = this.#values[idx] as [K, T]; + + const len = this.#values.length; + for (let i = 0; i < Math.min(idx, len - 1); i++) { + this.#values[(this.#headIdx + i + 1) % len] = + this.#values[(this.#headIdx + i) % len]; + } + this.#values[this.#headIdx] = entry; + return entry[1]; + } + push(key: K, value: T): void { + this.#headIdx = + (this.#headIdx - 1 + this.#values.length) % this.#values.length; + this.#values[this.#headIdx] = [key, value]; + } +} +const cache = new LRUCache(10); + +export default function runReactCompiler({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + const entry = cache.get(filename); + if ( + entry != null && + entry.sourceCode === sourceCode.text && + isDeepStrictEqual(entry.userOpts, userOpts) + ) { + return entry; + } else if (entry != null) { + if (process.env['DEBUG']) { + console.log( + `Cache hit for ${filename}, but source code or options changed, recomputing`, + ); + } + } + + const runEntry = runReactCompilerImpl({ + sourceCode, + filename, + userOpts, + }); + // If we have a cache entry, we can update it + if (entry != null) { + Object.assign(entry, runEntry); + } else { + cache.push(filename, runEntry); + } + return {...runEntry}; +} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index a159359773..a6041bd5cc 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -338,7 +338,16 @@ export async function transformFixtureInput( if (logs.length !== 0) { formattedLogs = logs .map(({event}) => { - return JSON.stringify(event); + return JSON.stringify(event, (key, value) => { + if ( + key === 'detail' && + value != null && + typeof value.serialize === 'function' + ) { + return value.serialize(); + } + return value; + }); }) .join('\n'); } diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 6e1bc7feeb..696261cbf5 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -542,11 +542,6 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - "@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" @@ -1605,7 +1600,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": +"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.27.0", "@babel/types@^7.27.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== @@ -1613,14 +1608,6 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -2148,6 +2135,11 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/diff-sequences@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + "@jest/environment@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz" @@ -2178,6 +2170,13 @@ "@types/node" "*" jest-mock "^29.5.0" +"@jest/expect-utils@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz#9d42e4b8bc80367db30abc6c42b2cb14073f66fc" + integrity sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew== + dependencies: + "@jest/get-type" "30.0.1" + "@jest/expect-utils@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz" @@ -2274,6 +2273,11 @@ jest-mock "^29.5.0" jest-util "^29.5.0" +"@jest/get-type@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.0.1.tgz#0d32f1bbfba511948ad247ab01b9007724fc9f52" + integrity sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw== + "@jest/globals@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz" @@ -2313,6 +2317,14 @@ "@jest/types" "^29.6.3" jest-mock "^29.7.0" +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + "@jest/reporters@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz" @@ -2435,6 +2447,13 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/schemas@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" @@ -2663,6 +2682,19 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz" @@ -2965,6 +2997,11 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sinclair/typebox@^0.34.0": + version "0.34.38" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.38.tgz#2365df7c23406a4d79413a766567bfbca708b49d" + integrity sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" @@ -3154,6 +3191,11 @@ resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== +"@types/istanbul-lib-coverage@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + "@types/istanbul-lib-report@*": version "3.0.0" resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" @@ -3176,6 +3218,13 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jest@^28.1.6": version "28.1.8" resolved "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz" @@ -3200,6 +3249,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + "@types/jsdom@^20.0.0": version "20.0.0" resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz" @@ -3282,6 +3339,11 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz" @@ -3309,6 +3371,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.33": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz" @@ -3740,7 +3809,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: +ansi-styles@^5.0.0, ansi-styles@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== @@ -3803,11 +3872,32 @@ aria-query@^5.0.0: resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.0.2.tgz" integrity sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q== +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + ast-types@^0.13.4: version "0.13.4" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz" @@ -3815,6 +3905,11 @@ ast-types@^0.13.4: dependencies: tslib "^2.0.1" +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + async@^3.2.3: version "3.2.6" resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" @@ -3825,6 +3920,13 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + axios@^1.6.1: version "1.7.4" resolved "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz" @@ -4245,7 +4347,7 @@ cac@^6.7.14: resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== @@ -4253,7 +4355,17 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -call-bound@^1.0.2: +call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== @@ -4295,7 +4407,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: +chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4377,6 +4489,11 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz" integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== +ci-info@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" @@ -4722,6 +4839,33 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + date-fns@^2.29.1: version "2.30.0" resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz" @@ -4788,6 +4932,24 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + degenerator@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz" @@ -4922,7 +5084,7 @@ dreamopt@~0.6.0: dependencies: wordwrap ">=0.0.2" -dunder-proto@^1.0.1: +dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== @@ -5033,7 +5195,67 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.1: +es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.9: + version "1.24.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" + integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== @@ -5050,6 +5272,25 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" + es5-ext@0.8.x: version "0.8.2" resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz" @@ -5428,6 +5669,18 @@ expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expect@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.0.5.tgz#c23bf193c5e422a742bfd2990ad990811de41a5a" + integrity sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ== + dependencies: + "@jest/expect-utils" "30.0.5" + "@jest/get-type" "30.0.1" + jest-matcher-utils "30.0.5" + jest-message-util "30.0.5" + jest-mock "30.0.5" + jest-util "30.0.5" + express-rate-limit@^7.5.0: version "7.5.0" resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz" @@ -5719,6 +5972,13 @@ follow-redirects@^1.15.6: resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz" @@ -5788,6 +6048,23 @@ function-bind@^1.1.2: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -5798,7 +6075,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -5819,7 +6096,7 @@ get-package-type@^0.1.0: resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proto@^1.0.1: +get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -5839,6 +6116,15 @@ get-stream@^6.0.0: resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + get-uri@^6.0.1: version "6.0.4" resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz" @@ -5957,6 +6243,14 @@ globals@^14.0.0: resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" @@ -5969,12 +6263,12 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.2.0: +gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -5989,6 +6283,11 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" @@ -5999,11 +6298,32 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.1.0: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" @@ -6231,6 +6551,15 @@ ini@^1.3.4: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" @@ -6251,6 +6580,15 @@ ipaddr.js@1.9.1: resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -6261,6 +6599,24 @@ is-arrayish@^0.3.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" @@ -6268,6 +6624,19 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-core-module@^2.9.0: version "2.10.0" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" @@ -6275,11 +6644,35 @@ is-core-module@^2.9.0: dependencies: has "^1.0.3" +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" @@ -6290,6 +6683,16 @@ is-generator-fn@^2.0.0: resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== + dependencies: + call-bound "^1.0.3" + get-proto "^1.0.0" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -6307,6 +6710,24 @@ is-interactive@^2.0.0: resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" @@ -6339,11 +6760,57 @@ is-promise@^4.0.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz" integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" @@ -6354,11 +6821,36 @@ is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0: resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2, is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-windows@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -6797,6 +7289,16 @@ jest-config@^29.7.0: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-diff@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.5.tgz#b40f81e0c0d13e5b81c4d62b0d0dfa6a524ee0fd" + integrity sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + pretty-format "30.0.5" + jest-diff@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz" @@ -7106,6 +7608,16 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-matcher-utils@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz#dff3334be58faea4a5e1becc228656fbbfc2467d" + integrity sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ== + dependencies: + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + jest-diff "30.0.5" + pretty-format "30.0.5" + jest-matcher-utils@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz" @@ -7146,6 +7658,21 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-message-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.5.tgz#dd12ffec91dd3fa6a59cbd538a513d8e239e070c" + integrity sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-message-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz" @@ -7206,6 +7733,15 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + jest-util "30.0.5" + jest-mock@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz" @@ -7237,6 +7773,11 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + jest-regex-util@^28.0.2: version "28.0.2" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" @@ -7683,6 +8224,18 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + jest-util@^28.0.0, jest-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" @@ -8327,7 +8880,7 @@ merge@^2.1.1: resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -8620,11 +9173,28 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.3: +object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + on-finished@^2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" @@ -8695,6 +9265,15 @@ ora@^7.0.1: string-width "^6.1.0" strip-ansi "^7.1.0" +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -8949,6 +9528,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + postcss-load-config@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz" @@ -8975,6 +9559,15 @@ prettier@^3.3.3: resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +pretty-format@30.0.5, pretty-format@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + pretty-format@^24: version "24.9.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz" @@ -9202,7 +9795,7 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -9251,6 +9844,20 @@ readline@^1.3.0: resolved "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz" integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz" @@ -9280,6 +9887,30 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" +regexp.escape@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexp.escape/-/regexp.escape-2.0.1.tgz#09e4beef9d202dbd739868f3818223f977cf91da" + integrity sha512-JItRb4rmyTzmERBkAf6J87LjDPy/RscIwmaJQ3gsFlAzrmZbZU8LwBw5IydFZXW9hqpgbPlGbMhtpqtuAhMgtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + for-each "^0.3.3" + safe-regex-test "^1.0.3" + +regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + regexpu-core@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz" @@ -9457,6 +10088,17 @@ rxjs@^7.0.0, rxjs@^7.8.1: dependencies: tslib "^2.1.0" +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" + isarray "^2.0.5" + safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" @@ -9467,6 +10109,23 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + safe-stable-stringify@^2.3.1: version "2.5.0" resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" @@ -9576,6 +10235,37 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" @@ -9759,6 +10449,13 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" @@ -9771,6 +10468,14 @@ stdin-discarder@^0.1.0: dependencies: bl "^5.0.0" +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + streamx@^2.15.0, streamx@^2.21.0: version "2.22.0" resolved "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz" @@ -9825,6 +10530,38 @@ string-width@^6.1.0: emoji-regex "^10.2.1" strip-ansi "^7.0.1" +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + +string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -10232,6 +10969,51 @@ type-is@^2.0.0, type-is@^2.0.1: media-typer "^1.1.0" mime-types "^3.0.0" +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + typed-query-selector@^2.12.0: version "2.12.0" resolved "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz" @@ -10251,6 +11033,16 @@ typescript@^5.4.3: resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" @@ -10460,11 +11252,64 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which@^1.2.10, which@^1.2.14: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" From 457e94f327dfbab9f700952cfbf3f7d533e1873d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 8 Aug 2025 12:18:50 -0400 Subject: [PATCH 420/422] [compiler] Aggregate error reporting, separate eslint rules All error messages that are not invariants, todos, or config validation errors are now moved to a registry. This helps us check that error messages and linked documentation is uniform and up to date. This also lets us link error codes to linter reporting categories and break the single `react-compiler` rule up into many smaller rules. Changes to error reporting in React Compiler: - instead of writing raw error message, add a new ErrorCode to `CompilerErrorCodes` - create errors by constructing a `CompilerErrorDetailOptions` with the correct error code, or calling `CompilerErrorDetail.fromCode()` / `CompilerDiagnostic.fromCode()`. Changes to linter: - `react-compiler` lint rule is now broken up into many smaller lint rules, each with their own test cases. - linting no longer fails early when a non-fatal error is find. Instead, we now log and move on to lint for more errors! Todos: - Codebases previously using the eslint plugin may already have `react-compiler` eslint suppressions. Since this PR removes this rule, these codebases may get new eslint warnings on already-suppressed ranges. Some options I can think of: - Force everyone to add the new suppressions - Release a script to rewrite previous suppressions to the new suppressions. Codebases that had incomplete suppressions / waited to update may benefit here, as this still surfaces errors on previously-unseen suppressions - Hard code logic in our eslint plugin to avoid reporting if we see a `eslint-disable-next-line react-compiler`. This doesn't feel ideal - Align on eslint plugin rule names and error code mappings - I aggregated a few error codes here. For example, reassigning and modifying local variables after render now have the same error code + message. See changes fixtures for more examples --- .../src/CompilerError.ts | 267 ++++-- .../src/Entrypoint/Imports.ts | 4 +- .../src/Entrypoint/Options.ts | 9 +- .../src/Entrypoint/Pipeline.ts | 36 +- .../src/Entrypoint/Program.ts | 47 +- .../src/Entrypoint/Suppression.ts | 14 +- .../ValidateNoUntransformedReferences.ts | 33 +- .../src/Flood/TypeErrors.ts | 41 +- .../src/HIR/BuildHIR.ts | 194 ++-- .../src/HIR/Environment.ts | 10 +- .../src/HIR/HIR.ts | 15 +- .../src/HIR/HIRBuilder.ts | 27 +- .../src/Inference/DropManualMemoization.ts | 77 +- .../src/Inference/InferFunctionEffects.ts | 36 +- .../Inference/InferMutationAliasingEffects.ts | 66 +- .../src/Inference/InferReferenceEffects.ts | 16 +- .../src/Transform/TransformFire.ts | 27 +- .../src/Utils/CompilerErrorCodes.ts | 611 ++++++++++++ .../src/Utils/CompilerErrorSeverity.ts | 34 + .../src/Validation/ValidateHooksUsage.ts | 58 +- .../ValidateLocalsNotReassignedAfterRender.ts | 19 +- .../ValidateMemoizedEffectDependencies.ts | 9 +- .../Validation/ValidateNoCapitalizedCalls.ts | 14 +- .../ValidateNoDerivedComputationsInEffects.ts | 21 +- ...ValidateNoFreezingKnownMutableFunctions.ts | 7 +- .../ValidateNoImpureFunctionsInRender.ts | 19 +- .../Validation/ValidateNoJSXInTryStatement.ts | 9 +- .../Validation/ValidateNoRefAccessInRender.ts | 101 +- .../Validation/ValidateNoSetStateInEffects.ts | 23 +- .../Validation/ValidateNoSetStateInRender.ts | 31 +- .../ValidatePreservedManualMemoization.ts | 46 +- .../Validation/ValidateStaticComponents.ts | 11 +- .../src/Validation/ValidateUseMemo.ts | 28 +- ...global-in-component-tag-function.expect.md | 4 +- ...or.assign-global-in-jsx-children.expect.md | 4 +- ...n-global-in-jsx-spread-attribute.expect.md | 6 +- ...rror.bailout-on-flow-suppression.expect.md | 2 +- ...ut-on-suppression-of-custom-rule.expect.md | 4 +- ...ext-variable-only-chained-assign.expect.md | 2 +- ...variable-in-function-declaration.expect.md | 2 +- ...erences-variable-its-assigned-to.expect.md | 2 +- ...destructure-assignment-to-global.expect.md | 4 +- ...ucture-to-local-global-variables.expect.md | 4 +- ...lid-global-reassignment-indirect.expect.md | 4 +- .../error.invalid-hoisting-setstate.expect.md | 4 +- ...valid-impure-functions-in-render.expect.md | 18 +- ...n-of-possible-props-phi-indirect.expect.md | 2 +- ...eassign-local-variable-in-effect.expect.md | 2 +- .../error.invalid-reassign-const.expect.md | 4 +- ...ssign-local-in-hook-return-value.expect.md | 2 +- ...eassign-local-variable-in-effect.expect.md | 2 +- ...-local-variable-in-hook-argument.expect.md | 2 +- ...n-local-variable-in-jsx-callback.expect.md | 2 +- ....invalid-sketchy-code-use-forget.expect.md | 4 +- ...alid-unclosed-eslint-suppression.expect.md | 2 +- ...nconditional-set-state-in-render.expect.md | 4 +- ...ange-shared-inner-outer-function.expect.md | 2 +- ...rror.mutate-property-from-global.expect.md | 2 +- ...or.not-useEffect-external-mutate.expect.md | 4 +- ...r.object-capture-global-mutation.expect.md | 6 +- .../error.reassign-global-fn-arg.expect.md | 4 +- ....reassignment-to-global-indirect.expect.md | 8 +- .../error.reassignment-to-global.expect.md | 8 +- ...ror.sketchy-code-exhaustive-deps.expect.md | 2 +- ...rror.sketchy-code-rules-of-hooks.expect.md | 2 +- .../error.store-property-in-global.expect.md | 2 +- ...ences-later-variable-declaration.expect.md | 2 +- ...state-in-render-after-loop-break.expect.md | 2 +- ...l-set-state-in-render-after-loop.expect.md | 2 +- ...-state-in-render-with-loop-throw.expect.md | 2 +- ...r.unconditional-set-state-lambda.expect.md | 2 +- ...tate-nested-function-expressions.expect.md | 2 +- ...ror.update-global-should-bailout.expect.md | 4 +- ...ror.useMemo-non-literal-depslist.expect.md | 4 +- .../dynamic-gating-invalid-multiple.expect.md | 2 +- .../no-emit/retry-no-emit.expect.md | 5 +- ...setState-in-useEffect-transitive.expect.md | 2 +- .../invalid-setState-in-useEffect.expect.md | 2 +- ...valid-impure-functions-in-render.expect.md | 18 +- ...n-local-variable-in-jsx-callback.expect.md | 2 +- ...rozen-hoisted-storecontext-const.expect.md | 4 +- ...or.not-useEffect-external-mutate.expect.md | 4 +- ....reassignment-to-global-indirect.expect.md | 8 +- .../error.reassignment-to-global.expect.md | 8 +- .../new-mutability/retry-no-emit.expect.md | 5 +- ....validate-useMemo-named-function.expect.md | 2 - .../bailout-retry/error.todo-syntax.expect.md | 2 +- ...ror.untransformed-fire-reference.expect.md | 2 +- .../bailout-retry/error.use-no-memo.expect.md | 2 +- .../error.invalid-outside-effect.expect.md | 4 +- ...id-rewrite-deps-no-array-literal.expect.md | 2 +- ...rror.invalid-rewrite-deps-spread.expect.md | 2 +- .../__tests__/ImpureFunctionCallsRule-test.ts | 32 + .../__tests__/InvalidHooksRule-test.ts | 85 ++ .../__tests__/NoAmbiguousJsxRule-test.ts | 31 + .../__tests__/NoCapitalizedCallsRule-test.ts | 58 ++ .../__tests__/NoRefAccessInRender-tests.ts | 27 + .../__tests__/NoSetStateInEffects-tests.ts | 48 + .../__tests__/NoSetStateInRender-test.ts | 58 ++ .../__tests__/NoUnusedDirectivesRule-test.ts | 58 ++ .../__tests__/PluginTest-test.ts | 172 ++++ .../__tests__/ReactCompilerRule-test.ts | 287 ------ .../ReactCompilerRuleTypescript-test.ts | 24 +- .../__tests__/StaticComponentsRule-test.ts | 44 + .../__tests__/UnnecessaryEffects-tests.ts | 36 + .../__tests__/ValidateUseMemo-test.ts | 43 + .../__tests__/shared-utils.ts | 98 ++ .../eslint-plugin-react-compiler/package.json | 4 +- .../eslint-plugin-react-compiler/src/index.ts | 52 +- .../src/rules/ReactCompilerRule.ts | 626 ++++++------ .../src/shared/RunReactCompiler.ts | 323 +++++++ compiler/packages/snap/src/compiler.ts | 11 +- compiler/yarn.lock | 901 +++++++++++++++++- 113 files changed, 3763 insertions(+), 1437 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts delete mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index ee0d0f74c5..6bce5dca2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -10,48 +10,22 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {SourceLocation} from './HIR'; import {Err, Ok, Result} from './Utils/Result'; import {assertExhaustive} from './Utils/utils'; - -export enum ErrorSeverity { - /** - * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some - * misunderstanding on the user’s part. - */ - InvalidJS = 'InvalidJS', - /** - * JS syntax that is not supported and which we do not plan to support. Developers should - * rewrite to use supported forms. - */ - UnsupportedJS = 'UnsupportedJS', - /** - * Code that breaks the rules of React. - */ - InvalidReact = 'InvalidReact', - /** - * Incorrect configuration of the compiler. - */ - InvalidConfig = 'InvalidConfig', - /** - * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve - * memoization. - */ - CannotPreserveMemoization = 'CannotPreserveMemoization', - /** - * Unhandled syntax that we don't support yet. - */ - Todo = 'Todo', - /** - * An unexpected internal error in the compiler that indicates critical issues that can panic - * the compiler. - */ - Invariant = 'Invariant', -} +import {ErrorSeverity} from './Utils/CompilerErrorSeverity'; +import { + ErrorCode, + ErrorCodeDetails, + LinterCategory, +} from './Utils/CompilerErrorCodes'; +export {ErrorSeverity}; +export {ErrorCode, ErrorCodeDetails, LinterCategory}; export type CompilerDiagnosticOptions = { severity: ErrorSeverity; category: string; - description: string; + description?: string | null | undefined; details: Array; suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; }; export type CompilerDiagnosticDetail = @@ -86,13 +60,28 @@ export type CompilerSuggestion = description: string; }; -export type CompilerErrorDetailOptions = { +export type PlainCompilerErrorDetailOptions = { + errorCode?: void; reason: string; description?: string | null | undefined; - severity: ErrorSeverity; + severity: + | ErrorSeverity.Invariant + | ErrorSeverity.Todo + | ErrorSeverity.InvalidConfig; loc: SourceLocation | null; suggestions?: Array | null | undefined; }; +export type CodedCompilerErrorDetailOptions = { + errorCode: ErrorCode; + description?: string | null | undefined; + loc: SourceLocation | null; + suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; +}; + +export type CompilerErrorDetailOptions = + | PlainCompilerErrorDetailOptions + | CodedCompilerErrorDetailOptions; export type PrintErrorMessageOptions = { /** @@ -102,19 +91,72 @@ export type PrintErrorMessageOptions = { eslint: boolean; }; +export function makeCompilerDiagnostic( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + }, +): CompilerDiagnostic { + return makeCompilerDiagnostic(code, options); +} + export class CompilerDiagnostic { options: CompilerDiagnosticOptions; - constructor(options: CompilerDiagnosticOptions) { + /** + * Constructor is private to enforce that we either only create invariant diagnostics + * or use ErrorCodes + */ + private constructor(options: CompilerDiagnosticOptions) { this.options = options; } - static create( - options: Omit, - ): CompilerDiagnostic { + static create< + T extends CompilerDiagnosticOptions & {severity: ErrorSeverity.Invariant}, + >(options: Omit): CompilerDiagnostic { return new CompilerDiagnostic({...options, details: []}); } + static fromCode( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + details?: Array | null | undefined; + }, + ): CompilerDiagnostic { + const errorEntry = ErrorCodeDetails[code]; + let description = undefined; + if (errorEntry.description != null) { + description = errorEntry.description; + } + if (options?.description != null && options.description.length > 0) { + if (description != null && description.length > 0) { + description += ' '; + } else { + description = ''; + } + description += options.description; + } + + const diagnosticOptions: CompilerDiagnosticOptions = { + severity: errorEntry.severity, + category: errorEntry.reason, + description, + linterCategory: errorEntry.linterCategory, + suggestions: options?.suggestions, + details: options?.details ?? [], + }; + + return new CompilerDiagnostic(diagnosticOptions); + } + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return {options: {...this.options, linterCategory: undefined}}; + } + get category(): CompilerDiagnosticOptions['category'] { return this.options.category; } @@ -127,6 +169,9 @@ export class CompilerDiagnostic { get suggestions(): CompilerDiagnosticOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): CompilerDiagnosticOptions['linterCategory'] { + return this.options.linterCategory; + } withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic { this.options.details.push(detail); @@ -138,11 +183,10 @@ export class CompilerDiagnostic { } printErrorMessage(source: string, options: PrintErrorMessageOptions): string { - const buffer = [ - printErrorSummary(this.severity, this.category), - '\n\n', - this.description, - ]; + const buffer = [printErrorSummary(this.severity, this.category)]; + if (this.description != null) { + buffer.push(`\n\n${this.description}`); + } for (const detail of this.options.details) { switch (detail.kind) { case 'error': { @@ -202,13 +246,65 @@ export class CompilerErrorDetail { this.options = options; } - get reason(): CompilerErrorDetailOptions['reason'] { - return this.options.reason; + static fromCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + return new CompilerErrorDetail({ + ...details, + errorCode: code, + } as CodedCompilerErrorDetailOptions); } - get description(): CompilerErrorDetailOptions['description'] { + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return { + options: { + reason: this.reason, + description: this.description, + severity: this.severity, + loc: this.loc, + suggestions: this.suggestions, + }, + }; + } + + get reason(): string { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].reason; + } else { + return this.options.reason; + } + } + get description(): string | null | undefined { + if (this.options.errorCode != null) { + let description = undefined; + if (ErrorCodeDetails[this.options.errorCode].description != null) { + description = ErrorCodeDetails[this.options.errorCode].description; + } + if ( + this.options.description != null && + this.options.description.length > 0 + ) { + if (description != null && description.length > 0) { + description += '. '; + } else { + description = ''; + } + description += this.options.description; + } + return description; + } return this.options.description; } - get severity(): CompilerErrorDetailOptions['severity'] { + get severity(): ErrorSeverity { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].severity; + } return this.options.severity; } get loc(): CompilerErrorDetailOptions['loc'] { @@ -217,6 +313,12 @@ export class CompilerErrorDetail { get suggestions(): CompilerErrorDetailOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): LinterCategory | null | undefined { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].linterCategory; + } + return undefined; + } primaryLocation(): SourceLocation | null { return this.loc; @@ -266,7 +368,7 @@ export class CompilerError extends Error { static invariant( condition: unknown, - options: Omit, + options: Omit, ): asserts condition { if (!condition) { const errors = new CompilerError(); @@ -280,14 +382,14 @@ export class CompilerError extends Error { } } - static throwDiagnostic(options: CompilerDiagnosticOptions): never { + static throwDiagnostic(diagnostic: CompilerDiagnostic): never { const errors = new CompilerError(); - errors.pushDiagnostic(new CompilerDiagnostic(options)); + errors.pushDiagnostic(diagnostic); throw errors; } static throwTodo( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -296,34 +398,21 @@ export class CompilerError extends Error { throw errors; } - static throwInvalidJS( - options: Omit, + static throwFromCode( + code: ErrorCode, + options?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, ): never { const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidJS, - }), - ); - throw errors; - } - - static throwInvalidReact( - options: Omit, - ): never { - const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidReact, - }), - ); + errors.pushErrorDetail(CompilerErrorDetail.fromCode(code, options)); throw errors; } static throwInvalidConfig( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -392,13 +481,10 @@ export class CompilerError extends Error { } push(options: CompilerErrorDetailOptions): CompilerErrorDetail { - const detail = new CompilerErrorDetail({ - reason: options.reason, - description: options.description ?? null, - severity: options.severity, - suggestions: options.suggestions, - loc: typeof options.loc === 'symbol' ? null : options.loc, - }); + if (options instanceof CompilerErrorDetail) { + return this.pushErrorDetail(options); + } + const detail = new CompilerErrorDetail(options); return this.pushErrorDetail(detail); } @@ -407,6 +493,19 @@ export class CompilerError extends Error { return detail; } + pushErrorCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + const detail = CompilerErrorDetail.fromCode(code, details); + this.details.push(detail); + return detail; + } + hasErrors(): boolean { return this.details.length > 0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 24ce37cf72..ea1d28a62e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -38,10 +38,10 @@ export function validateRestrictedImports( ImportDeclaration(importDeclPath) { if (restrictedImports.has(importDeclPath.node.source.value)) { error.push({ - severity: ErrorSeverity.Todo, reason: 'Bailing out due to blocklisted import', description: `Import from module ${importDeclPath.node.source.value}`, loc: importDeclPath.node.loc ?? null, + severity: ErrorSeverity.Todo, }); } }, @@ -205,10 +205,10 @@ export class ProgramContext { } const error = new CompilerError(); error.push({ - severity: ErrorSeverity.Todo, reason: 'Encountered conflicting global in generated program', description: `Conflict from local binding ${name}`, loc: scope.getBinding(name)?.path.node.loc ?? null, + severity: ErrorSeverity.Todo, suggestions: null, }); return Err(error); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c13940ed10..1a8e1457ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerErrorDetail, - CompilerErrorDetailOptions, + PlainCompilerErrorDetailOptions, } from '../CompilerError'; import { EnvironmentConfig, @@ -107,6 +107,8 @@ export type PluginOptions = { * passes. * * Defaults to false + * + * TODO: rename this to lintOnly or something similar */ noEmit: boolean; @@ -234,7 +236,10 @@ export type CompileErrorEvent = { export type CompileDiagnosticEvent = { kind: 'CompileDiagnostic'; fnLoc: t.SourceLocation | null; - detail: Omit, 'suggestions'>; + detail: Omit< + Omit, + 'suggestions' + >; }; export type CompileSuccessEvent = { kind: 'CompileSuccess'; 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 a4f984f195..7a004fa18c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -100,7 +100,6 @@ import {outlineJSX} from '../Optimization/OutlineJsx'; import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls'; import {transformFire} from '../Transform'; import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender'; -import {CompilerError} from '..'; import {validateStaticComponents} from '../Validation/ValidateStaticComponents'; import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions'; import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects'; @@ -174,7 +173,7 @@ function runWithEnvironment( !env.config.disableMemoizationForDebugging && !env.config.enableChangeDetectionForDebugging ) { - dropManualMemoization(hir).unwrap(); + env.logOrThrowErrors(dropManualMemoization(hir)); log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } @@ -207,10 +206,10 @@ function runWithEnvironment( if (env.isInferredMemoEnabled) { if (env.config.validateHooksUsage) { - validateHooksUsage(hir).unwrap(); + env.logOrThrowErrors(validateHooksUsage(hir)); } - if (env.config.validateNoCapitalizedCalls) { - validateNoCapitalizedCalls(hir).unwrap(); + if (env.config.validateNoCapitalizedCalls != null) { + env.logOrThrowErrors(validateNoCapitalizedCalls(hir)); } } @@ -230,20 +229,17 @@ function runWithEnvironment( log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); if (!env.config.enableNewMutationAliasingModel) { - const fnEffectErrors = inferReferenceEffects(hir); + const fnEffectResult = inferReferenceEffects(hir); + if (env.isInferredMemoEnabled) { - if (fnEffectErrors.length > 0) { - CompilerError.throw(fnEffectErrors[0]); - } + env.logOrThrowErrors(fnEffectResult); } log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); } else { const mutabilityAliasingErrors = inferMutationAliasingEffects(hir); log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } + env.logOrThrowErrors(mutabilityAliasingErrors); } } @@ -272,10 +268,8 @@ function runWithEnvironment( }); log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } - validateLocalsNotReassignedAfterRender(hir); + env.logOrThrowErrors(mutabilityAliasingErrors.map(() => undefined)); + env.logOrThrowErrors(validateLocalsNotReassignedAfterRender(hir)); } } @@ -285,15 +279,15 @@ function runWithEnvironment( } if (env.config.validateRefAccessDuringRender) { - validateNoRefAccessInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoRefAccessInRender(hir)); } if (env.config.validateNoSetStateInRender) { - validateNoSetStateInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoSetStateInRender(hir)); } if (env.config.validateNoDerivedComputationsInEffects) { - validateNoDerivedComputationsInEffects(hir); + env.logOrThrowErrors(validateNoDerivedComputationsInEffects(hir)); } if (env.config.validateNoSetStateInEffects) { @@ -305,14 +299,14 @@ function runWithEnvironment( } if (env.config.validateNoImpureFunctionsInRender) { - validateNoImpureFunctionsInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoImpureFunctionsInRender(hir)); } if ( env.config.validateNoFreezingKnownMutableFunctions || env.config.enableNewMutationAliasingModel ) { - validateNoFreezingKnownMutableFunctions(hir).unwrap(); + env.logOrThrowErrors(validateNoFreezingKnownMutableFunctions(hir)); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 79bbee37a5..825f83a2a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -7,11 +7,7 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, ErrorSeverity} from '../CompilerError'; import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; @@ -32,6 +28,7 @@ import { } from './Suppression'; import {GeneratedSource} from '../HIR'; import {Err, Ok, Result} from '../Utils/Result'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; export type CompilerPass = { opts: PluginOptions; @@ -101,12 +98,9 @@ function findDirectivesDynamicGating( if (t.isValidIdentifier(maybeMatch[1])) { result.push({directive, match: maybeMatch[1]}); } else { - errors.push({ - reason: `Dynamic gating directive is not a valid JavaScript identifier`, + errors.pushErrorCode(ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, { description: `Found '${directive.value.value}'`, - severity: ErrorSeverity.InvalidReact, loc: directive.loc ?? null, - suggestions: null, }); } } @@ -115,14 +109,11 @@ function findDirectivesDynamicGating( return Err(errors); } else if (result.length > 1) { const error = new CompilerError(); - error.push({ - reason: `Multiple dynamic gating directives found`, + error.pushErrorCode(ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, { description: `Expected a single directive but found [${result .map(r => r.directive.value.value) .join(', ')}]`, - severity: ErrorSeverity.InvalidReact, loc: result[0].directive.loc ?? null, - suggestions: null, }); return Err(error); } else if (result.length === 1) { @@ -451,14 +442,12 @@ export function compileProgram( if (programContext.hasModuleScopeOptOut) { if (compiledFns.length > 0) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: - 'Unexpected compiled functions when module scope opt-out is present', - severity: ErrorSeverity.Invariant, - loc: null, - }), - ); + error.push({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }); handleError(error, programContext, null); } return null; @@ -591,9 +580,7 @@ function processFn( let compiledFn: CodegenFunction; const compileResult = tryCompileFunction(fn, fnType, programContext); if (compileResult.kind === 'error') { - if (directives.optOut != null) { - logError(compileResult.error, programContext, fn.node.loc ?? null); - } else { + if (directives.optOut == null) { handleError(compileResult.error, programContext, fn.node.loc ?? null); } const retryResult = retryCompileFunction(fn, fnType, programContext); @@ -692,7 +679,7 @@ function tryCompileFunction( fn, programContext.opts.environment, fnType, - 'all_features', + programContext.opts.noEmit ? 'lint_only' : 'all_features', programContext, programContext.opts.logger, programContext.filename, @@ -805,15 +792,7 @@ function shouldSkipCompilation( if (pass.opts.sources) { if (pass.filename === null) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: `Expected a filename but found none.`, - description: - "When the 'sources' config options is specified, the React compiler will only compile files with a name", - severity: ErrorSeverity.InvalidConfig, - loc: null, - }), - ); + error.pushErrorCode(ErrorCode.FILENAME_NOT_SET); handleError(error, pass, null); return true; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts index ee341b111f..75828aa108 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, - ErrorSeverity, + ErrorCode, } from '../CompilerError'; import {assertExhaustive} from '../Utils/utils'; import {GeneratedSource} from '../HIR'; @@ -165,14 +165,12 @@ export function suppressionsToCompilerError( let reason, suggestion; switch (suppressionRange.source) { case 'Eslint': - reason = - 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled'; + reason = ErrorCode.BAILOUT_ESLINT_SUPPRESSION; suggestion = 'Remove the ESLint suppression and address the React error'; break; case 'Flow': - reason = - 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow'; + reason = ErrorCode.BAILOUT_FLOW_SUPPRESSION; suggestion = 'Remove the Flow suppression and address the React error'; break; default: @@ -182,10 +180,8 @@ export function suppressionsToCompilerError( ); } error.pushDiagnostic( - CompilerDiagnostic.create({ - category: reason, - description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``, - severity: ErrorSeverity.InvalidReact, + CompilerDiagnostic.fromCode(reason, { + description: `Found suppression \`${suppressionRange.disableComment.value.trim()}\``, suggestions: [ { description: suggestion, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 16d7c3713c..5271d66886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -8,27 +8,24 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..'; +import {CompilerError, EnvironmentConfig, Logger} from '..'; import {getOrInsertWith} from '../Utils/utils'; import {Environment, GeneratedSource} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; import {CompileProgramMetadata} from './Program'; -import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError'; +import {CompilerDiagnostic} from '../CompilerError'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; -function throwInvalidReact( - options: Omit, +function logAndThrowDiagnostic( + diagnostic: CompilerDiagnostic, {logger, filename}: TraversalState, ): never { - const detail: CompilerDiagnosticOptions = { - severity: ErrorSeverity.InvalidReact, - ...options, - }; logger?.logEvent(filename, { kind: 'CompileError', fnLoc: null, - detail: new CompilerDiagnostic(detail), + detail: diagnostic, }); - CompilerError.throwDiagnostic(detail); + CompilerError.throwDiagnostic(diagnostic); } function isAutodepsSigil( @@ -90,10 +87,8 @@ function assertValidEffectImportReference( * as it may have already been transformed by the compiler (and not * memoized). */ - throwInvalidReact( - { - category: - 'Cannot infer dependencies of this effect. This will break your build!', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.DID_NOT_INFER_DEPS, { description: 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' + (maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''), @@ -104,7 +99,7 @@ function assertValidEffectImportReference( loc: parent.node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } @@ -121,10 +116,8 @@ function assertValidFireImportReference( paths[0], context.transformErrors, ); - throwInvalidReact( - { - category: - '[Fire] Untransformed reference to compiler-required feature.', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.CANNOT_COMPILE_FIRE, { description: 'Either remove this `fire` call or ensure it is successfully transformed by the compiler' + maybeErrorDiagnostic @@ -137,7 +130,7 @@ function assertValidFireImportReference( loc: paths[0].node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts b/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts index fa3f551ff5..85d6bd0457 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts @@ -1,4 +1,5 @@ import {CompilerError, SourceLocation} from '..'; +import {ErrorCode} from '../CompilerError'; import { ConcreteType, printConcrete, @@ -12,8 +13,8 @@ export function unsupportedLanguageFeature( desc: string, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Typedchecker does not currently support language feature: ${desc}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Typedchecker does not currently support language feature: ${desc}`, loc, }); } @@ -49,16 +50,16 @@ export function raiseUnificationErrors( loc, }); } else if (errs.length === 1) { - CompilerError.throwInvalidJS({ - reason: `Unable to unify types because ${printUnificationError(errs[0])}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to unify types because ${printUnificationError(errs[0])}`, loc, }); } else { const messages = errs .map(err => `\t* ${printUnificationError(err)}`) .join('\n'); - CompilerError.throwInvalidJS({ - reason: `Unable to unify types because:\n${messages}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to unify types because:\n${messages}`, loc, }); } @@ -69,21 +70,21 @@ export function unresolvableTypeVariable( id: VariableId, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Unable to resolve free variable ${id} to a concrete type`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to resolve free variable ${id} to a concrete type`, loc, }); } export function cannotAddVoid(explicit: boolean, loc: SourceLocation): never { if (explicit) { - CompilerError.throwInvalidJS({ - reason: `Undefined is not a valid operand of \`+\``, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Undefined is not a valid operand of \`+\``, loc, }); } else { - CompilerError.throwInvalidJS({ - reason: `Value may be undefined, which is not a valid operand of \`+\``, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Value may be undefined, which is not a valid operand of \`+\``, loc, }); } @@ -93,8 +94,8 @@ export function unsupportedTypeAnnotation( desc: string, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Typedchecker does not currently support type annotation: ${desc}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Typedchecker does not currently support type annotation: ${desc}`, loc, }); } @@ -106,16 +107,16 @@ export function checkTypeArgumentArity( loc: SourceLocation, ): void { if (expected !== actual) { - CompilerError.throwInvalidJS({ - reason: `Expected ${desc} to have ${expected} type parameters, got ${actual}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Expected ${desc} to have ${expected} type parameters, got ${actual}`, loc, }); } } export function notAFunction(desc: string, loc: SourceLocation): void { - CompilerError.throwInvalidJS({ - reason: `Cannot call ${desc} because it is not a function`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Cannot call ${desc} because it is not a function`, loc, }); } @@ -124,8 +125,8 @@ export function notAPolymorphicFunction( desc: string, loc: SourceLocation, ): void { - CompilerError.throwInvalidJS({ - reason: `Cannot call ${desc} with type arguments because it is not a polymorphic function`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Cannot call ${desc} with type arguments because it is not a polymorphic function`, loc, }); } 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 3b11670146..26f05f58ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -12,6 +12,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, + ErrorCode, ErrorSeverity, } from '../CompilerError'; import {Err, Ok, Result} from '../Utils/Result'; @@ -170,14 +171,12 @@ export function lower( ); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.Todo, - category: `Handle ${param.node.type} parameters`, + CompilerDiagnostic.fromCode(ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, { description: `[BuildHIR] Add support for ${param.node.type} parameters.`, }).withDetail({ kind: 'error', loc: param.node.loc ?? null, - message: 'Unsupported parameter type', + message: `Unsupported parameter type: ${param.node.type}`, }), ); } @@ -201,14 +200,12 @@ export function lower( directives = body.get('directives').map(d => d.node.value.value); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidJS, - category: `Unexpected function body kind`, + CompilerDiagnostic.fromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`, }).withDetail({ kind: 'error', loc: body.node.loc ?? null, - message: 'Expected a block statement or expression', + message: 'Change this to a block statement or expression', }), ); } @@ -769,12 +766,12 @@ function lowerStatement( const testExpr = case_.get('test'); if (testExpr.node == null) { if (hasDefault) { - builder.errors.push({ - reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, - severity: ErrorSeverity.InvalidJS, - loc: case_.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + { + loc: case_.node.loc ?? null, + }, + ); break; } hasDefault = true; @@ -886,19 +883,20 @@ function lowerStatement( if (builder.isContextIdentifier(id)) { if (kind === InstructionKind.Const) { const declRangeStart = declaration.parentPath.node.start!; - builder.errors.push({ - reason: `Expect \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: id.node.loc ?? null, - suggestions: [ - { - description: 'Change to a `let` declaration', - op: CompilerSuggestionOperation.Replace, - range: [declRangeStart, declRangeStart + 5], // "const".length - text: 'let', - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: id.node.loc ?? null, + suggestions: [ + { + description: 'Change to a `let` declaration', + op: CompilerSuggestionOperation.Replace, + range: [declRangeStart, declRangeStart + 5], // "const".length + text: 'let', + }, + ], + }, + ); } lowerValueToTemporary(builder, { kind: 'DeclareContext', @@ -932,13 +930,13 @@ function lowerStatement( } } } else { - builder.errors.push({ - reason: `Expected variable declaration to be an identifier if no initializer was provided`, - description: `Got a \`${id.type}\``, - severity: ErrorSeverity.InvalidJS, - loc: stmt.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + { + description: `Got a \`${id.type}\``, + loc: stmt.node.loc ?? null, + }, + ); } } return; @@ -1374,12 +1372,8 @@ function lowerStatement( return; } case 'WithStatement': { - builder.errors.push({ - reason: `JavaScript 'with' syntax is not supported`, - description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_WITH, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1394,12 +1388,8 @@ function lowerStatement( * and complex enough to support that we don't anticipate supporting anytime soon. Developers * are encouraged to lift classes out of component/hook declarations. */ - builder.errors.push({ - reason: 'Inline `class` declarations are not supported', - description: `Move class declarations outside of components/hooks`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_INNER_CLASS, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1423,12 +1413,8 @@ function lowerStatement( case 'ImportDeclaration': case 'TSExportAssignment': case 'TSImportEqualsDeclaration': { - builder.errors.push({ - reason: - 'JavaScript `import` and `export` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_IMPORT_EXPORT, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1438,12 +1424,8 @@ function lowerStatement( return; } case 'TSNamespaceExportDeclaration': { - builder.errors.push({ - reason: - 'TypeScript `namespace` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_TS_NAMESPACE, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1698,12 +1680,8 @@ function lowerExpression( const expr = exprPath as NodePath; const calleePath = expr.get('callee'); if (!calleePath.isExpression()) { - builder.errors.push({ - reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, - description: `Got a \`${calleePath.node.type}\``, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_NEW_EXPRESSION, { loc: calleePath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -1800,12 +1778,12 @@ function lowerExpression( last = lowerExpressionToTemporary(builder, item); } if (last === null) { - builder.errors.push({ - reason: `Expected sequence expression to have at least one expression`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + { + loc: expr.node.loc ?? null, + }, + ); } else { lowerValueToTemporary(builder, { kind: 'StoreLocal', @@ -2289,18 +2267,18 @@ function lowerExpression( }); for (const [name, locations] of Object.entries(fbtLocations)) { if (locations.length > 1) { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support duplicate fbt tags', - description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, - details: locations.map(loc => { - return { - kind: 'error', - message: `Multiple \`<${tagName}:${name}>\` tags found`, - loc, - }; + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_DUPLICATE_FBT_TAGS, { + description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, + details: locations.map(loc => { + return { + kind: 'error', + message: `Multiple \`<${tagName}:${name}>\` tags found`, + loc, + }; + }), }), - }); + ); } } } @@ -2389,11 +2367,8 @@ function lowerExpression( const quasis = expr.get('quasis'); if (subexprs.length !== quasis.length - 1) { - builder.errors.push({ - reason: `Unexpected quasi and subexpression lengths in template literal`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_QUASI_LENGTHS, { loc: exprPath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -2441,24 +2416,23 @@ function lowerExpression( }; } } else { - builder.errors.push({ - reason: `Only object properties can be deleted`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: [ - { - description: 'Remove this line', - range: [expr.node.start!, expr.node.end!], - op: CompilerSuggestionOperation.Remove, - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + { + loc: expr.node.loc ?? null, + suggestions: [ + { + description: 'Remove this line', + range: [expr.node.start!, expr.node.end!], + op: CompilerSuggestionOperation.Remove, + }, + ], + }, + ); return {kind: 'UnsupportedNode', node: expr.node, loc: exprLoc}; } } else if (expr.node.operator === 'throw') { - builder.errors.push({ - reason: `Throw expressions are not supported`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_THROW_EXPRESSION, { loc: expr.node.loc ?? null, suggestions: [ { @@ -3285,10 +3259,8 @@ function lowerJsxElementName( const name = exprPath.node.name.name; const tag = `${namespace}:${name}`; if (namespace.indexOf(':') !== -1 || name.indexOf(':') !== -1) { - builder.errors.push({ - reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + builder.errors.pushErrorCode(ErrorCode.INVALID_JSX_NAMESPACED_NAME, { description: `Got \`${namespace}\` : \`${name}\``, - severity: ErrorSeverity.InvalidJS, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3583,11 +3555,7 @@ function lowerIdentifier( } default: { if (binding.kind === 'Global' && binding.name === 'eval') { - builder.errors.push({ - reason: `The 'eval' function is not supported`, - description: - 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_EVAL, { loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3653,9 +3621,7 @@ function lowerIdentifierForAssignment( binding.bindingKind === 'const' && kind === InstructionKind.Reassign ) { - builder.errors.push({ - reason: `Cannot reassign a \`const\` variable`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, { loc: path.node.loc ?? null, description: binding.identifier.name != null @@ -3710,12 +3676,13 @@ function lowerAssignment( let temporary; if (builder.isContextIdentifier(lvalue)) { if (kind === InstructionKind.Const && !isHoistedIdentifier) { - builder.errors.push({ - reason: `Expected \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: lvalue.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: lvalue.node.loc ?? null, + suggestions: null, + }, + ); } if ( @@ -3726,7 +3693,8 @@ function lowerAssignment( ) { builder.errors.push({ reason: `Unexpected context variable kind`, - severity: ErrorSeverity.InvalidJS, + description: `Expected one of Const, Reassign, Let, Function, got ${kind}`, + severity: ErrorSeverity.Invariant, loc: lvalue.node.loc ?? null, suggestions: null, }); 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 957c5ab84a..ad1815a62d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -93,7 +93,7 @@ export const MacroSchema = z.union([ z.tuple([z.string(), z.array(MacroMethodSchema)]), ]); -export type CompilerMode = 'all_features' | 'no_inferred_memo'; +export type CompilerMode = 'all_features' | 'no_inferred_memo' | 'lint_only'; export type Macro = z.infer; export type MacroMethod = z.infer; @@ -829,6 +829,14 @@ export class Environment { } } + logOrThrowErrors(errors: Result): void { + if (this.compilerMode === 'lint_only') { + this.logErrors(errors); + } else { + errors.unwrap(); + } + } + isContextIdentifier(node: t.Identifier): boolean { return this.#contextIdentifiers.has(node); } 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 51715b3c1e..89b2c0fc41 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -15,6 +15,7 @@ import {Type, makeType} from './Types'; import {z} from 'zod'; import type {AliasingEffect} from '../Inference/AliasingEffects'; import {isReservedWord} from '../Utils/Keyword'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -1322,18 +1323,18 @@ export function forkTemporaryIdentifier( */ export function makeIdentifierName(name: string): ValidatedIdentifier { if (isReservedWord(name)) { - CompilerError.throwInvalidJS({ - reason: 'Expected a non-reserved identifier name', - loc: GeneratedSource, - description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, - suggestions: null, - }); + CompilerError.throwFromCode( + ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + { + loc: GeneratedSource, + description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, + }, + ); } else { CompilerError.invariant(t.isValidIdentifier(name), { reason: `Expected a valid identifier name`, loc: GeneratedSource, description: `\`${name}\` is not a valid JavaScript identifier`, - suggestions: null, }); } return { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 81959ea361..1d71452d0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -7,7 +7,7 @@ import {Binding, NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {Environment} from './Environment'; import { BasicBlock, @@ -37,6 +37,7 @@ import { mapTerminalSuccessors, terminalFallthrough, } from './visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -308,19 +309,17 @@ export default class HIRBuilder { resolveBinding(node: t.Identifier): Identifier { if (node.name === 'fbt') { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support local variables named `fbt`', - description: - 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', - details: [ - { - kind: 'error', - message: 'Rename to avoid conflict with fbt plugin', - loc: node.loc ?? GeneratedSource, - }, - ], - }); + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, { + details: [ + { + kind: 'error', + message: 'Rename to avoid conflict with fbt plugin', + loc: node.loc ?? GeneratedSource, + }, + ], + }), + ); } const originalName = node.name; let name = originalName; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 7aeb3edb22..fa9bddd3f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -5,12 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, - SourceLocation, -} from '..'; +import {CompilerDiagnostic, CompilerError, SourceLocation} from '..'; import { CallExpression, Effect, @@ -35,6 +30,7 @@ import { makeInstructionId, } from '../HIR'; import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; type ManualMemoCallee = { @@ -299,12 +295,11 @@ function extractManualMemoizationArgs( >; if (fnPlace == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected a callback function to be passed to ${kind}`, - description: `Expected a callback function to be passed to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + kind === 'useMemo' + ? ErrorCode.INVALID_USE_MEMO_NO_ARG0 + : ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Expected a callback function to be passed to ${kind}`, @@ -314,12 +309,11 @@ function extractManualMemoizationArgs( } if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Unexpected spread argument to ${kind}`, - description: `Unexpected spread argument to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + fnPlace.kind === 'Spread' + ? ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT + : ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Unexpected spread argument to ${kind}`, @@ -334,12 +328,9 @@ function extractManualMemoizationArgs( ); if (maybeDepsList == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list for ${kind} to be an array literal`, - description: `Expected the dependency list for ${kind} to be an array literal`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + ).withDetail({ kind: 'error', loc: depsListPlace.loc, message: `Expected the dependency list for ${kind} to be an array literal`, @@ -352,12 +343,9 @@ function extractManualMemoizationArgs( const maybeDep = sidemap.maybeDeps.get(dep.identifier.id); if (maybeDep == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + ).withDetail({ kind: 'error', loc: dep.loc, message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, @@ -457,16 +445,16 @@ export function dropManualMemoization( if (funcToCheck !== undefined && funcToCheck.loweredFunc.func) { if (!hasNonVoidReturn(funcToCheck.loweredFunc.func)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks must return a value', - description: `This ${ - manualMemo.loadInstr.value.kind === 'PropertyLoad' - ? 'React.useMemo' - : 'useMemo' - } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + { + description: `This ${ + manualMemo.loadInstr.value.kind === 'PropertyLoad' + ? 'React.useMemo' + : 'useMemo' + } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, + }, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: 'useMemo() callbacks must return a value', @@ -497,12 +485,9 @@ export function dropManualMemoization( */ if (!sidemap.functions.has(fnPlace.identifier.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the first argument to be an inline function expression`, - description: `Expected the first argument to be an inline function expression`, - suggestions: [], - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + ).withDetail({ kind: 'error', loc: fnPlace.loc, message: `Expected the first argument to be an inline function expression`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts index a01ca188a0..5fcf8a3cae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts @@ -25,6 +25,7 @@ import { isRefOrRefValue, } from '../HIR'; import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {assertExhaustive} from '../Utils/utils'; interface State { @@ -62,22 +63,20 @@ function inferOperandEffect(state: State, place: Place): null | FunctionEffect { // We ignore mutations of primitives since this is not a React-specific problem value.kind !== ValueKind.Primitive ) { - let reason = getWriteErrorReason(value); + let errorCode = getWriteErrorReason(value); return { kind: value.reason.size === 1 && value.reason.has(ValueReason.Global) ? 'GlobalMutation' : 'ReactMutation', error: { - reason, + errorCode, description: place.identifier.name !== null && place.identifier.name.kind === 'named' ? `Found mutation of \`${place.identifier.name.value}\`` : null, loc: place.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }; } @@ -266,11 +265,8 @@ export function inferInstructionFunctionEffects( functionEffects.push({ kind: 'GlobalMutation', error: { - reason: - 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + errorCode: ErrorCode.INVALID_WRITE_GLOBAL, loc: instr.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }); break; @@ -324,28 +320,28 @@ function isEffectSafeOutsideRender(effect: FunctionEffect): boolean { return effect.kind === 'GlobalMutation'; } -export function getWriteErrorReason(abstractValue: AbstractValue): string { +export function getWriteErrorReason(abstractValue: AbstractValue): ErrorCode { if (abstractValue.reason.has(ValueReason.Global)) { - return 'Modifying a variable defined outside a component or hook is not allowed. Consider using an effect'; + return ErrorCode.INVALID_WRITE_GLOBAL; } else if (abstractValue.reason.has(ValueReason.JsxCaptured)) { - return 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX'; + return ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX; } else if (abstractValue.reason.has(ValueReason.Context)) { - return `Modifying a value returned from 'useContext()' is not allowed.`; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT; } else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) { - return 'Modifying a value returned from a function whose return value should not be mutated'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE; } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) { - return 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS; } else if (abstractValue.reason.has(ValueReason.State)) { - return "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead"; + return ErrorCode.INVALID_WRITE_STATE; } else if (abstractValue.reason.has(ValueReason.ReducerState)) { - return "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead"; + return ErrorCode.INVALID_WRITE_REDUCER_STATE; } else if (abstractValue.reason.has(ValueReason.Effect)) { - return 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()'; + return ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY; } else if (abstractValue.reason.has(ValueReason.HookCaptured)) { - return 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook'; + return ErrorCode.INVALID_WRITE_HOOK_CAPTURED; } else if (abstractValue.reason.has(ValueReason.HookReturn)) { - return 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed'; + return ErrorCode.INVALID_WRITE_HOOK_RETURN; } else { - return 'This modifies a variable that React considers immutable'; + return ErrorCode.INVALID_WRITE_GENERIC; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index 2adf78fe05..60146d8335 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -9,7 +9,6 @@ import { CompilerDiagnostic, CompilerError, Effect, - ErrorSeverity, SourceLocation, ValueKind, } from '..'; @@ -69,6 +68,7 @@ import {getWriteErrorReason} from './InferFunctionEffects'; import prettyFormat from 'pretty-format'; import {createTemporaryPlace} from '../HIR/HIRBuilder'; import {AliasingEffect, AliasingSignature, hashEffect} from './AliasingEffects'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; const DEBUG = false; @@ -442,7 +442,7 @@ function applySignature( const value = state.kind(effect.value); switch (value.kind) { case ValueKind.Frozen: { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -455,10 +455,9 @@ function applySignature( effects.push({ kind: 'MutateFrozen', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -1026,22 +1025,20 @@ function applyEffect( const hoistedAccess = context.hoistedContextDeclarations.get( effect.value.identifier.declarationId, ); - const diagnostic = CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access variable before it is declared', - description: `${variable ?? 'This variable'} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.`, - }); + const diagnostic = CompilerDiagnostic.fromCode( + ErrorCode.INVALID_ACCESS_BEFORE_INIT, + ); if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) { diagnostic.withDetail({ kind: 'error', loc: hoistedAccess.loc, - message: `${variable ?? 'variable'} accessed before it is declared`, + message: `${variable ?? 'This variable'} is accessed before it is declared`, }); } diagnostic.withDetail({ kind: 'error', loc: effect.value.loc, - message: `${variable ?? 'variable'} is declared here`, + message: `${variable ?? 'This variable'} is declared here`, }); applyEffect( @@ -1056,7 +1053,7 @@ function applyEffect( effects, ); } else { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -1075,10 +1072,9 @@ function applyEffect( ? 'MutateFrozen' : 'MutateGlobal', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -2006,15 +2002,12 @@ function computeSignatureForInstruction( effects.push({ kind: 'MutateGlobal', place: value.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'Cannot reassign variables declared outside of the component/hook', - description: `Variable ${variable} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)`, - }).withDetail({ + error: CompilerDiagnostic.fromCode( + ErrorCode.INVALID_WRITE_GLOBAL, + ).withDetail({ kind: 'error', loc: instr.loc, - message: `${variable} cannot be reassigned`, + message: `${variable} should not be reassigned`, }), }); effects.push({kind: 'Assign', from: value.value, into: lvalue}); @@ -2105,19 +2098,16 @@ function computeEffectsForLegacySignature( effects.push({ kind: 'Impure', place: receiver, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - }).withDetail({ - kind: 'error', - loc, - message: 'Cannot call impure function', - }), + error: CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail( + { + kind: 'error', + loc, + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', + }, + ), }); } const stores: Array = []; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 1b0856791a..a806ebd4bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError'; +import {CompilerError} from '../CompilerError'; import {Environment} from '../HIR'; import { AbstractValue, @@ -48,6 +48,7 @@ import { eachTerminalOperand, eachTerminalSuccessor, } from '../HIR/visitors'; +import {Err, Ok, Result} from '../Utils/Result'; import {assertExhaustive, Set_isSuperset} from '../Utils/utils'; import { inferTerminalFunctionEffects, @@ -106,7 +107,7 @@ const UndefinedValue: InstructionValue = { export default function inferReferenceEffects( fn: HIRFunction, options: {isFunctionExpression: boolean} = {isFunctionExpression: false}, -): Array { +): Result { /* * Initial state contains function params * TODO: include module declarations here as well @@ -247,10 +248,17 @@ export default function inferReferenceEffects( if (options.isFunctionExpression) { fn.effects = functionEffects; - return []; } else { - return transformFunctionEffectErrors(functionEffects); + const errors = transformFunctionEffectErrors(functionEffects); + if (errors.length > 0) { + const compilerError = new CompilerError(); + for (const detail of errors) { + compilerError.push(detail); + } + return Err(compilerError); + } } + return Ok(void 0); } type FreezeAction = {values: Set; reason: Set}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts index f88c85f2f0..dbb84944b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts @@ -42,6 +42,7 @@ import { import {eachInstructionOperand} from '../HIR/visitors'; import {printSourceLocationLine} from '../HIR/PrintHIR'; import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * TODO(jmbrown): @@ -50,8 +51,6 @@ import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; * - React.useEffect calls */ -const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`'; - export function transformFire(fn: HIRFunction): void { const context = new Context(fn.env); replaceFireFunctions(fn, context); @@ -178,9 +177,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } else if (value.args.length > 1 && value.args[1].kind === 'Spread') { @@ -188,9 +185,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].place.loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -243,11 +238,9 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } else { context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description: '`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed', - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else { @@ -262,10 +255,8 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else if (value.kind === 'CallExpression') { @@ -394,9 +385,7 @@ function ensureNoRemainingCalleeCaptures( description: `All uses of ${calleeName} must be either used with a fire() call in \ this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \ ${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -411,9 +400,7 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { context.pushError({ loc: place.identifier.loc, description: 'Cannot use `fire` outside of a useEffect function', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts new file mode 100644 index 0000000000..2fd3244fc5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts @@ -0,0 +1,611 @@ +import {ErrorSeverity} from './CompilerErrorSeverity'; + +export enum LinterCategory { + RULES_OF_HOOKS = 'rules-of-hooks', + IMPURE_FUNCTIONS = 'impure-functions', + JSX_IN_TRY = 'jsx-in-try', + NO_REF_ACCESS_IN_RENDER = 'no-ref-access-in-render', + VALIDATE_MANUAL_MEMO = 'validate-manual-memo', + DYNAMIC_MANUAL_MEMO = 'dynamic-manual-memo', + + EXHAUSTIVE_DEPS = 'exhaustive-deps', + MEMOIZED_DEPENDENCIES = 'memoized-dependencies', // not real! + INVALID_WRITE = 'invalid-write', + CAPITALIZED_CALLS = 'capitalized-calls', + STATIC_COMPONENTS = 'static-components', + NO_SET_STATE_IN_RENDER = 'no-set-state-in-render', + NO_SET_STATE_IN_EFFECTS = 'no-set-state-in-effects', + UNNECESSARY_EFFECTS = 'unnecessary-effects', + + UNSUPPORTED_SYNTAX = 'unsupported-syntax', + + TODO_SYNTAX = 'todo-syntax', + + COMPILER_CONFIG = 'compiler-config', +} + +export enum ErrorCode { + HOOK_CALL_STATIC, + HOOK_INVALID_REFERENCE, + HOOK_CALL_REACTIVE, + HOOK_CALL_NOT_TOP_LEVEL, + IMPURE_FUNCTIONS, + JSX_IN_TRY, + NO_REF_ACCESS_IN_RENDER, + WRITE_AFTER_RENDER, + REASSIGN_IN_ASYNC, + INVALID_WRITE, + CAPITALIZED_CALLS, + STATIC_COMPONENTS, + INVALID_USE_MEMO_CALLBACK_RETURN, + INVALID_USE_MEMO_CALLBACK_PARAMETERS, + INVALID_USE_MEMO_CALLBACK_ASYNC, + INVALID_USE_MEMO_NO_ARG0, + INVALID_USE_CALLBACK_NO_ARG0, + DYNAMIC_MANUAL_MEMO_CALLBACK, + DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + + INVALID_SET_STATE_IN_RENDER, + INVALID_SET_STATE_IN_MEMO, + INVALID_SET_STATE_IN_EFFECTS, + + NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + + DYNAMIC_GATING_IS_NOT_IDENTIFIER, + DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + FILENAME_NOT_SET, + + INVALID_WRITE_GLOBAL, + INVALID_WRITE_FROZEN_VALUE_JSX, + INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + INVALID_WRITE_IMMUTABLE_ARGS, + INVALID_WRITE_STATE, + INVALID_WRITE_REDUCER_STATE, + INVALID_WRITE_EFFECT_DEPENDENCY, + INVALID_WRITE_HOOK_CAPTURED, + INVALID_WRITE_HOOK_RETURN, + INVALID_ACCESS_BEFORE_INIT, + INVALID_WRITE_GENERIC, + + MEMOIZED_EFFECT_DEPENDENCIES, + + INVALID_SYNTAX_MULTIPLE_DEFAULTS, + INVALID_SYNTAX_REASSIGNED_CONST, + INVALID_SYNTAX_BAD_VARIABLE_DECL, + UNSUPPORTED_WITH, + UNSUPPORTED_INNER_CLASS, + INVALID_IMPORT_EXPORT, + INVALID_TS_NAMESPACE, + UNSUPPORTED_NEW_EXPRESSION, + UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + INVALID_QUASI_LENGTHS, + INVALID_SYNTAX_DELETE_EXPRESSION, + UNSUPPORTED_THROW_EXPRESSION, + INVALID_JSX_NAMESPACED_NAME, + UNSUPPORTED_EVAL, + INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + INVALID_JAVASCRIPT_AST, + BAILOUT_ESLINT_SUPPRESSION, + BAILOUT_FLOW_SUPPRESSION, + MANUAL_MEMO_MUTATED_LATER, + MANUAL_MEMO_REMOVED, + MANUAL_MEMO_DEPENDENCIES_CONFLICT, + + CANNOT_COMPILE_FIRE, + DID_NOT_INFER_DEPS, + + /** Todo syntax */ + TODO_CONFLICTING_FBT_IDENTIFIER, + TODO_DUPLICATE_FBT_TAGS, + UNKNOWN_FUNCTION_PARAMETERS, +} + +type ErrorCodeType = { + code: ErrorCode; + description?: string; + severity: ErrorSeverity; + reason: string; + linterCategory: LinterCategory | null; +}; + +export const ErrorCodeDetails: Record = { + [ErrorCode.HOOK_CALL_STATIC]: { + code: ErrorCode.HOOK_CALL_STATIC, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_INVALID_REFERENCE]: { + code: ErrorCode.HOOK_INVALID_REFERENCE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_REACTIVE]: { + code: ErrorCode.HOOK_CALL_REACTIVE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_NOT_TOP_LEVEL]: { + code: ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.IMPURE_FUNCTIONS]: { + code: ErrorCode.IMPURE_FUNCTIONS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot call impure functions during render', + description: + 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).', + linterCategory: LinterCategory.IMPURE_FUNCTIONS, + }, + [ErrorCode.JSX_IN_TRY]: { + code: ErrorCode.JSX_IN_TRY, + severity: ErrorSeverity.InvalidReact, + reason: 'Avoid constructing JSX within try/catch', + description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, + linterCategory: LinterCategory.JSX_IN_TRY, + }, + [ErrorCode.NO_REF_ACCESS_IN_RENDER]: { + code: ErrorCode.NO_REF_ACCESS_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access refs during render', + description: + 'React refs are values that are not needed for rendering. Refs should only be accessed ' + + 'outside of render, such as in event handlers or effects. ' + + 'Accessing a ref value (the `current` property) during render can cause your component ' + + 'not to update as expected (https://react.dev/reference/react/useRef)', + linterCategory: LinterCategory.NO_REF_ACCESS_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_EFFECTS]: { + code: ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Calling setState synchronously within an effect can trigger cascading renders', + description: + 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + + 'In general, the body of an effect should do one or both of the following:\n' + + '* Update external systems with the latest state from React.\n' + + '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + + 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + + '(https://react.dev/learn/you-might-not-need-an-effect)', + linterCategory: LinterCategory.NO_SET_STATE_IN_EFFECTS, + }, + [ErrorCode.WRITE_AFTER_RENDER]: { + code: ErrorCode.WRITE_AFTER_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot modify local variables after render completes', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.REASSIGN_IN_ASYNC]: { + code: ErrorCode.REASSIGN_IN_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variable in async function', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE]: { + code: ErrorCode.INVALID_WRITE, + severity: ErrorSeverity.InvalidReact, + reason: 'This value cannot be modified', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.CAPITALIZED_CALLS]: { + code: ErrorCode.CAPITALIZED_CALLS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config', + linterCategory: LinterCategory.CAPITALIZED_CALLS, + }, + [ErrorCode.STATIC_COMPONENTS]: { + code: ErrorCode.STATIC_COMPONENTS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot create components during render', + linterCategory: LinterCategory.STATIC_COMPONENTS, + }, + [ErrorCode.INVALID_USE_MEMO_NO_ARG0]: { + code: ErrorCode.INVALID_USE_MEMO_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useMemo`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_CALLBACK_NO_ARG0]: { + code: ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useCallback`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useMemo', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + + [ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useCallback', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not accept parameters', + description: + 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not be async or generator functions', + description: + 'useMemo() callbacks are called once and must synchronously return a value.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks must return a value', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the first argument to be an inline function expression`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list of useMemo or useCallback to be an array literal`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY]: { + code: ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_SET_STATE_IN_RENDER]: { + code: ErrorCode.INVALID_SET_STATE_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState during render may trigger an infinite loop', + description: + 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_MEMO]: { + code: ErrorCode.INVALID_SET_STATE_IN_MEMO, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState from useMemo may trigger an infinite loop', + description: + 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS]: { + code: ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', + linterCategory: LinterCategory.UNNECESSARY_EFFECTS, + }, + + /** Invalid writes */ + [ErrorCode.INVALID_WRITE_GLOBAL]: { + code: ErrorCode.INVALID_WRITE_GLOBAL, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variables declared outside of the component/hook', + description: + 'Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX]: { + code: ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + severity: ErrorSeverity.InvalidReact, + reason: `Modifying a value returned from 'useContext()' is not allowed.`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a function whose return value should not be mutated', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_STATE]: { + code: ErrorCode.INVALID_WRITE_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_REDUCER_STATE]: { + code: ErrorCode.INVALID_WRITE_REDUCER_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY]: { + code: ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_CAPTURED]: { + code: ErrorCode.INVALID_WRITE_HOOK_CAPTURED, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_RETURN]: { + code: ErrorCode.INVALID_WRITE_HOOK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_ACCESS_BEFORE_INIT]: { + code: ErrorCode.INVALID_ACCESS_BEFORE_INIT, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access variable before it is declared', + description: `Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_GENERIC]: { + code: ErrorCode.INVALID_WRITE_GENERIC, + severity: ErrorSeverity.InvalidReact, + reason: 'This modifies a variable that React considers immutable', + linterCategory: LinterCategory.INVALID_WRITE, + }, + + /** Compiler Config */ + [ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER]: { + code: ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, + severity: ErrorSeverity.InvalidReact, + reason: 'Dynamic gating directive is not a valid JavaScript identifier', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES]: { + code: ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + severity: ErrorSeverity.InvalidReact, + reason: 'Expected a single dynamic gating directive', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.FILENAME_NOT_SET]: { + code: ErrorCode.FILENAME_NOT_SET, + severity: ErrorSeverity.InvalidConfig, + reason: `Expected a filename but found none.`, + description: + "When the 'sources' config options is specified, the React compiler will only compile files with a name", + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + + /** Effect dependencies */ + [ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES]: { + code: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, + reason: + 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.MEMOIZED_DEPENDENCIES, + }, + + /** Invalid / unsupported syntax */ + [ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS]: { + code: ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST]: { + code: ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + reason: `Expect \`const\` declaration not to be reassigned`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL]: { + code: ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + reason: `Expected variable declaration to be an identifier if no initializer was provided`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_WITH]: { + code: ErrorCode.UNSUPPORTED_WITH, + reason: `JavaScript 'with' syntax is not supported`, + description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_INNER_CLASS]: { + code: ErrorCode.UNSUPPORTED_INNER_CLASS, + reason: 'Inline `class` declarations are not supported', + description: `Move class declarations outside of components/hooks`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_IMPORT_EXPORT]: { + code: ErrorCode.INVALID_IMPORT_EXPORT, + reason: + 'JavaScript `import` and `export` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_TS_NAMESPACE]: { + code: ErrorCode.INVALID_TS_NAMESPACE, + reason: + 'TypeScript `namespace` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_NEW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_NEW_EXPRESSION, + reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + reason: `Expected sequence expression to have at least one expression`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_QUASI_LENGTHS]: { + code: ErrorCode.INVALID_QUASI_LENGTHS, + reason: `Unexpected quasi and subexpression lengths in template literal`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION]: { + code: ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + reason: `Only object properties can be deleted`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_THROW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_THROW_EXPRESSION, + reason: `Throw expressions are not supported`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JSX_NAMESPACED_NAME]: { + code: ErrorCode.INVALID_JSX_NAMESPACED_NAME, + reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EVAL]: { + code: ErrorCode.UNSUPPORTED_EVAL, + reason: `The 'eval' function is not supported`, + description: + 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME]: { + code: ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + reason: 'Expected a non-reserved identifier name', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JAVASCRIPT_AST]: { + code: ErrorCode.INVALID_JAVASCRIPT_AST, + reason: 'Encountered invalid JavaScript', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.BAILOUT_ESLINT_SUPPRESSION]: { + code: ErrorCode.BAILOUT_ESLINT_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_REMOVED]: { + code: ErrorCode.MANUAL_MEMO_REMOVED, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + + /** + * This is left vague as fire is very experimental + */ + [ErrorCode.CANNOT_COMPILE_FIRE]: { + code: ErrorCode.CANNOT_COMPILE_FIRE, + reason: 'Cannot compile `fire`', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.DID_NOT_INFER_DEPS]: { + code: ErrorCode.DID_NOT_INFER_DEPS, + reason: + 'Cannot infer dependencies of this effect. This will break your build!', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + [ErrorCode.BAILOUT_FLOW_SUPPRESSION]: { + code: ErrorCode.BAILOUT_FLOW_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + /** Syntax React Compiler may eventually support */ + [ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER]: { + code: ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, + reason: 'Support local variables named `fbt`', + description: + 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.TODO_DUPLICATE_FBT_TAGS]: { + code: ErrorCode.TODO_DUPLICATE_FBT_TAGS, + reason: 'Support duplicate fbt tags', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.UNKNOWN_FUNCTION_PARAMETERS]: { + code: ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, + severity: ErrorSeverity.Todo, + reason: 'Currently unsupported function parameter syntax', + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_MUTATED_LATER]: { + code: ErrorCode.MANUAL_MEMO_MUTATED_LATER, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: [ + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', + 'This dependency may be mutated later, which could cause the value to change unexpectedly.', + ].join(''), + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT]: { + code: ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + '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.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts new file mode 100644 index 0000000000..f399cadb9d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts @@ -0,0 +1,34 @@ +export enum ErrorSeverity { + /** + * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some + * misunderstanding on the user’s part. + */ + InvalidJS = 'InvalidJS', + /** + * JS syntax that is not supported and which we do not plan to support. Developers should + * rewrite to use supported forms. + */ + UnsupportedJS = 'UnsupportedJS', + /** + * Code that breaks the rules of React. + */ + InvalidReact = 'InvalidReact', + /** + * Incorrect configuration of the compiler. + */ + InvalidConfig = 'InvalidConfig', + /** + * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve + * memoization. + */ + CannotPreserveMemoization = 'CannotPreserveMemoization', + /** + * Unhandled syntax that we don't support yet. + */ + Todo = 'Todo', + /** + * An unexpected internal error in the compiler that indicates critical issues that can panic + * the compiler. + */ + Invariant = 'Invariant', +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts index b28228339c..d04e163960 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts @@ -6,11 +6,7 @@ */ import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, CompilerErrorDetail} from '../CompilerError'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {isHookName} from '../HIR/Environment'; import { @@ -27,6 +23,7 @@ import { } from '../HIR/visitors'; import {assertExhaustive} from '../Utils/utils'; import {Result} from '../Utils/Result'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; /** * Represents the possible kinds of value which may be stored at a given Place during @@ -111,8 +108,6 @@ export function validateHooksUsage( // Once a particular hook has a conditional call error, don't report any further issues for this hook setKind(place, Kind.Error); - const reason = - 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)'; const previousError = typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined; @@ -120,15 +115,15 @@ export function validateHooksUsage( * In some circumstances such as optional calls, we may first encounter a "hook may not be referenced as normal values" error. * If that same place is also used as a conditional call, upgrade the error to a conditonal hook error */ - if (previousError === undefined || previousError.reason !== reason) { + if ( + previousError === undefined || + previousError.reason !== + ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason + ) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason, + CompilerErrorDetail.fromCode(ErrorCode.HOOK_CALL_STATIC, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -139,13 +134,8 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + CompilerErrorDetail.fromCode(ErrorCode.HOOK_INVALID_REFERENCE, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -156,14 +146,12 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', - loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }), + CompilerErrorDetail.fromCode( + ErrorCodeDetails[ErrorCode.HOOK_CALL_REACTIVE].code, + { + loc: place.loc, + }, + ), ); } } @@ -424,7 +412,7 @@ export function validateHooksUsage( } for (const [, error] of errorsByPlace) { - errors.push(error); + errors.pushErrorDetail(error); } return errors.asResult(); } @@ -446,16 +434,10 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void { : instr.value.property; const hookKind = getHookKind(fn.env, callee.identifier); if (hookKind != null) { - errors.pushErrorDetail( - new CompilerErrorDetail({ - severity: ErrorSeverity.InvalidReact, - reason: - 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', - loc: callee.loc, - description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, - suggestions: null, - }), - ); + errors.pushErrorCode(ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, { + loc: callee.loc, + description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, + }); } break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts index 31bbf8c94d..b6c3038915 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; import {HIRFunction, IdentifierId, Place} from '../HIR'; import { eachInstructionLValue, @@ -13,13 +13,17 @@ import { eachTerminalOperand, } from '../HIR/visitors'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Ok, Result} from '../Utils/Result'; /** * Validates that local variables cannot be reassigned after render. * This prevents a category of bugs in which a closure captures a * binding from one render but does not update */ -export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { +export function validateLocalsNotReassignedAfterRender( + fn: HIRFunction, +): Result { const contextVariables = new Set(); const reassignment = getContextReassignment( fn, @@ -35,9 +39,7 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable after render completes', + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER, { description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }).withDetail({ kind: 'error', @@ -45,8 +47,9 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { message: `Cannot reassign ${variable} after render completes`, }), ); - throw errors; + return errors.asResult(); } + return Ok(undefined); } function getContextReassignment( @@ -90,9 +93,7 @@ function getContextReassignment( ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable in async function', + CompilerDiagnostic.fromCode(ErrorCode.REASSIGN_IN_ASYNC, { description: 'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead', }).withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts index b33cfb1512..90c714c557 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity} from '..'; +import {CompilerError} from '..'; import { Identifier, Instruction, @@ -22,6 +22,7 @@ import { ReactiveFunctionVisitor, visitReactiveFunction, } from '../ReactiveScopes/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -108,12 +109,8 @@ class Visitor extends ReactiveFunctionVisitor { isUnmemoized(deps.identifier, this.scopes)) ) { state.push({ - reason: - 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', - description: null, - severity: ErrorSeverity.CannotPreserveMemoization, + errorCode: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null, - suggestions: null, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts index 8989cb1ac2..72e237f660 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..'; +import {CompilerError, EnvironmentConfig} from '..'; import {HIRFunction, IdentifierId} from '../HIR'; import {DEFAULT_GLOBALS} from '../HIR/Globals'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateNoCapitalizedCalls( @@ -33,8 +34,6 @@ export function validateNoCapitalizedCalls( const errors = new CompilerError(); const capitalLoadGlobals = new Map(); const capitalizedProperties = new Map(); - const reason = - 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config'; for (const [, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { switch (value.kind) { @@ -55,11 +54,9 @@ export function validateNoCapitalizedCalls( const calleeIdentifier = value.callee.identifier.id; const calleeName = capitalLoadGlobals.get(calleeIdentifier); if (calleeName != null) { - CompilerError.throwInvalidReact({ - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${calleeName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; @@ -78,12 +75,9 @@ export function validateNoCapitalizedCalls( const propertyIdentifier = value.property.identifier.id; const propertyName = capitalizedProperties.get(propertyIdentifier); if (propertyName != null) { - errors.push({ - severity: ErrorSeverity.InvalidReact, - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${propertyName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts index d026a94ed4..5ec74f0dae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity, SourceLocation} from '..'; +import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, BlockId, @@ -19,6 +19,8 @@ import { eachInstructionValueOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Result} from '../Utils/Result'; /** * Validates that useEffect is not used for derived computations which could/should @@ -43,7 +45,9 @@ import { * const fullName = firstName + ' ' + lastName; * ``` */ -export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { +export function validateNoDerivedComputationsInEffects( + fn: HIRFunction, +): Result { const candidateDependencies: Map = new Map(); const functions: Map = new Map(); const locals: Map = new Map(); @@ -96,9 +100,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { } } } - if (errors.hasErrors()) { - throw errors; - } + return errors.asResult(); } function validateEffect( @@ -218,13 +220,6 @@ function validateEffect( } for (const loc of setStateLocations) { - errors.push({ - reason: - 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', - description: null, - severity: ErrorSeverity.InvalidReact, - loc, - suggestions: null, - }); + errors.pushErrorCode(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, {loc}); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts index 7a79c74780..f829e4b92a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts @@ -5,7 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; +import {ErrorCode} from '../CompilerError'; import { FunctionEffect, HIRFunction, @@ -65,9 +66,7 @@ export function validateNoFreezingKnownMutableFunctions( ? `\`${place.identifier.name.value}\`` : 'a local variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot modify local variables after render completes', + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER, { description: `This argument is a function which may reassign or mutate ${variable} after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts index 85adb79ceb..ee452fc08a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {HIRFunction} from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -35,19 +36,13 @@ export function validateNoImpureFunctionsInRender( ); if (signature != null && signature.impure === true) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail({ kind: 'error', loc: callee.loc, - message: 'Cannot call impure function', + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts index eea6c0a08e..bab00d7159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts @@ -5,8 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {BlockId, HIRFunction} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -35,11 +36,7 @@ export function validateNoJSXInTryStatement( case 'JsxExpression': case 'JsxFragment': { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Avoid constructing JSX within try/catch', - description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.JSX_IN_TRY).withDetail({ kind: 'error', loc: value.loc, message: 'Avoid constructing JSX within try/catch', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts index e1c17625f4..1ffc5d013c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { BlockId, HIRFunction, @@ -26,6 +22,7 @@ import { eachPatternOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Err, Ok, Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -467,11 +464,9 @@ function validateNoRefAccessInRenderImpl( if (fnType.fn.readRefEffect) { didError = true; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.NO_REF_ACCESS_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, message: `This function accesses a ref value`, @@ -730,15 +725,13 @@ function destructure( function guardCheck(errors: CompilerError, operand: Place, env: Env): void { if (env.get(operand.identifier.id)?.kind === 'Guard') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -754,15 +747,13 @@ function validateNoRefValueAccess( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -780,15 +771,13 @@ function validateNoRefPassedToFunction( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Passing a ref to a function may read its value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Passing a ref to a function may read its value during render`, + }, + ), ); } } @@ -802,15 +791,13 @@ function validateNoRefUpdate( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'Ref' || type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Cannot update ref during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Cannot update ref during render`, + }, + ), ); } } @@ -823,21 +810,13 @@ function validateNoDirectRefValueAccess( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: type.loc ?? operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: type.loc ?? operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } - -const ERROR_DESCRIPTION = - 'React refs are values that are not needed for rendering. Refs should only be accessed ' + - 'outside of render, such as in event handlers or effects. ' + - 'Accessing a ref value (the `current` property) during render can cause your component ' + - 'not to update as expected (https://react.dev/reference/react/useRef)'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts index 9c4efa380c..0a5638277d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { HIRFunction, IdentifierId, @@ -20,6 +16,7 @@ import { Place, } from '../HIR'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -95,19 +92,9 @@ export function validateNoSetStateInEffects( const setState = setStateFunctions.get(arg.identifier.id); if (setState !== undefined) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState synchronously within an effect can trigger cascading renders', - description: - 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + - 'In general, the body of an effect should do one or both of the following:\n' + - '* Update external systems with the latest state from React.\n' + - '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + - 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + - '(https://react.dev/learn/you-might-not-need-an-effect)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + ).withDetail({ kind: 'error', loc: setState.loc, message: diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts index 81209d61c6..dc67540ee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts @@ -5,14 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, isSetStateType} from '../HIR'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -127,14 +124,9 @@ function validateNoSetStateInRenderImpl( ) { if (activeManualMemoId !== null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState from useMemo may trigger an infinite loop', - description: - 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_MEMO, + ).withDetail({ kind: 'error', loc: callee.loc, message: 'Found setState() within useMemo()', @@ -142,17 +134,12 @@ function validateNoSetStateInRenderImpl( ); } else if (unconditionalBlocks.has(block.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState during render may trigger an infinite loop', - description: - 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, - message: 'Found setState() within useMemo()', + message: 'Found setState() call here', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index 6bb59247da..7776e15cbb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError, ErrorCode} from '../CompilerError'; import { DeclarationId, Effect, @@ -280,13 +276,8 @@ function validateInferredDep( } } errorState.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - '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. ', + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, { + description: DEBUG || // If the dependency is a named variable then we can report it. Otherwise only print in debug mode (dep.identifier.name != null && dep.identifier.name.kind === 'named') @@ -300,9 +291,6 @@ function validateInferredDep( : 'Inferred dependency not present in source' }.` : '', - ] - .join('') - .trim(), suggestions: null, }).withDetail({ kind: 'error', @@ -534,15 +522,9 @@ class Visitor extends ReactiveFunctionVisitor { !this.prunedScopes.has(identifier.scope.id) ) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', - 'This dependency may be mutated later, which could cause the value to change unexpectedly.', - ].join(''), - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.MANUAL_MEMO_MUTATED_LATER, + ).withDetail({ kind: 'error', loc, message: 'This dependency may be modified later', @@ -582,18 +564,10 @@ class Visitor extends ReactiveFunctionVisitor { for (const identifier of decls) { if (isUnmemoized(identifier, this.scopes)) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. ', - DEBUG - ? `${printIdentifier(identifier)} was not memoized.` - : '', - ] - .join('') - .trim(), + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_REMOVED, { + description: DEBUG + ? `${printIdentifier(identifier)} was not memoized.` + : '', }).withDetail({ kind: 'error', loc, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts index 7f5fb408b4..12722fa2d5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, SourceLocation} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -64,9 +61,7 @@ export function validateStaticComponents( ); if (location != null) { error.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot create components during render', + CompilerDiagnostic.fromCode(ErrorCode.STATIC_COMPONENTS, { description: `Components created during render will reset their state each time they are created. Declare components outside of render. `, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts index 69ab401c89..05531ff211 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateUseMemo(fn: HIRFunction): Result { @@ -73,13 +70,9 @@ export function validateUseMemo(fn: HIRFunction): Result { ? firstParam.loc : firstParam.place.loc; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks may not accept parameters', - description: - 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + ).withDetail({ kind: 'error', loc, message: 'Callbacks with parameters are not supported', @@ -89,14 +82,9 @@ export function validateUseMemo(fn: HIRFunction): Result { if (body.loweredFunc.func.async || body.loweredFunc.func.generator) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'useMemo() callbacks may not be async or generator functions', - description: - 'useMemo() callbacks are called once and must synchronously return a value.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + ).withDetail({ kind: 'error', loc: body.loc, message: 'Async and generator functions are not supported', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md index ce42e65125..e47107723f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-component-tag-function.ts:3:4 1 | function Component() { 2 | const Foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | return ; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md index ee57ea6eb0..5acfcde730 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md @@ -22,13 +22,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-jsx-children.ts:3:4 1 | function Component() { 2 | const foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | // Children are generally access/called during render, so 6 | // modifying a global in a children function is almost diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md index 8476885de7..eb2516e386 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md @@ -18,13 +18,15 @@ function Component() { ``` Found 1 error: -Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.assign-global-in-jsx-spread-attribute.ts:4:4 2 | function Component() { 3 | const foo = () => { > 4 | someGlobal = true; - | ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + | ^^^^^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | return
; 7 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md index 6e522e1666..a5530a1180 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md @@ -20,7 +20,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `$FlowFixMe[react-rule-hook]` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `$FlowFixMe[react-rule-hook]` error.bailout-on-flow-suppression.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index 3221f97731..b348bc6191 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -23,7 +23,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable my-app/react-rule` error.bailout-on-suppression-of-custom-rule.ts:3:0 1 | // @eslintSuppressionRules:["my-app","react-rule"] @@ -36,7 +36,7 @@ error.bailout-on-suppression-of-custom-rule.ts:3:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line my-app/react-rule` error.bailout-on-suppression-of-custom-rule.ts:7:2 5 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md index 6e9887c5ac..d6e0a07d7a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md @@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md index e5c28e6e36..71f96f1ab5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md @@ -19,7 +19,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md index a8a83f6b11..ef6f6091a6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md @@ -17,7 +17,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `callback` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md index 4b49c5f653..96485fb584 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md @@ -17,12 +17,12 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `x` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-assignment-to-global.ts:2:3 1 | function useFoo(props) { > 2 | [x] = props; - | ^ `x` cannot be reassigned + | ^ `x` should not be reassigned 3 | return {x}; 4 | } 5 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md index 6da3b558bd..bae0df94af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-to-local-global-variables.ts:3:6 1 | function Component(props) { 2 | let a; > 3 | [a, b] = props.value; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 4 | 5 | return [a, b]; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md index 8e8b7917d7..f3f2cff6e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md @@ -39,13 +39,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-global-reassignment-indirect.ts:9:4 7 | 8 | const setGlobal = () => { > 9 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 10 | }; 11 | const indirectSetGlobal = () => { 12 | setGlobal(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md index 291d3873b4..8c512ae350 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md @@ -42,13 +42,13 @@ Found 1 error: Error: Cannot access variable before it is declared -`setState` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized error.invalid-hoisting-setstate.ts:19:18 17 | * $2 = Function context=setState 18 | */ > 19 | useEffect(() => setState(2), []); - | ^^^^^^^^ `setState` accessed before it is declared + | ^^^^^^^^ `setState` is accessed before it is declared 20 | 21 | const [state, setState] = useState(0); 22 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md index 3155d64329..cf6b33ce8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md index 4ac7af5bae..768e0300bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md @@ -23,7 +23,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.invalid-mutation-of-possible-props-phi-indirect.ts:4:4 2 | let x = cond ? someGlobal : props.foo; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md index 437fbcb38c..de59216e60 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md @@ -48,7 +48,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md index 25b9a5c186..72bfa49422 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md @@ -15,7 +15,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign a `const` variable +Error: Expect `const` declaration not to be reassigned `x` is declared as const. @@ -23,7 +23,7 @@ error.invalid-reassign-const.ts:3:2 1 | function Component() { 2 | const x = 0; > 3 | x = 1; - | ^ Cannot reassign a `const` variable + | ^ Expect `const` declaration not to be reassigned 4 | } 5 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md index 6379515a05..1b5942449b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md @@ -17,7 +17,7 @@ function useFoo() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md index 368b312022..8a27203097 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md @@ -49,7 +49,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md index 8c7973377d..309fd02906 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md @@ -50,7 +50,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index 3ecbcc97c3..1b4f26b2c2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -43,7 +43,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md index 96be8584be..14d6068b97 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ @@ -32,7 +32,7 @@ error.invalid-sketchy-code-use-forget.ts:1:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:5:2 3 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md index e19cee7532..d77327b90d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md @@ -40,7 +40,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-unclosed-eslint-suppression.ts:2:0 1 | // Note: Everything below this is sketchy diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md index fa9e20a418..f10764dc70 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md @@ -29,7 +29,7 @@ error.invalid-unconditional-set-state-in-render.ts:6:2 4 | const aliased = setX; 5 | > 6 | setX(1); - | ^^^^ Found setState() within useMemo() + | ^^^^ Found setState() call here 7 | aliased(2); 8 | 9 | return x; @@ -42,7 +42,7 @@ error.invalid-unconditional-set-state-in-render.ts:7:2 5 | 6 | setX(1); > 7 | aliased(2); - | ^^^^^^^ Found setState() within useMemo() + | ^^^^^^^ Found setState() call here 8 | 9 | return x; 10 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md index 337b9dd30c..db7b07261e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md @@ -34,7 +34,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `a` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md index 78530caf4a..9903cf2a1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.mutate-property-from-global.ts:4:9 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md index c7b1ac5f45..508aea8d27 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:5:4 3 | function Component(props) { @@ -34,7 +34,7 @@ error.not-useEffect-external-mutate.ts:5:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md index 89bcedf956..606c1c42c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md @@ -24,13 +24,15 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Modifying a variable defined outside a component or hook is not allowed. Consider using an effect +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.object-capture-global-mutation.ts:4:4 2 | function Foo() { 3 | const x = () => { > 4 | window.href = 'foo'; - | ^^^^^^ Modifying a variable defined outside a component or hook is not allowed. Consider using an effect + | ^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | const y = {x}; 7 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md index 2c409ea5b5..cd7f202a1c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md @@ -28,13 +28,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassign-global-fn-arg.ts:5:4 3 | export default function MyApp() { 4 | const fn = () => { > 5 | b = 2; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 6 | }; 7 | return foo(fn); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md index 8835f19ad1..6963940109 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md @@ -21,26 +21,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:4:4 2 | const foo = () => { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | }; 7 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | }; 7 | foo(); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md index 4d259dd8d4..88ae3a7ae8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md @@ -18,26 +18,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:3:2 1 | function Component() { 2 | // Cannot assign to globals > 3 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 4 | moduleLocal = true; 5 | } 6 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | // Cannot assign to globals 3 | someUnknownGlobal = true; > 4 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 5 | } 6 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md index 9c87cafff1..0bcfae2e9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md @@ -24,7 +24,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` error.sketchy-code-exhaustive-deps.ts:6:7 4 | () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md index 7077b733b0..bd1b3ed4c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md @@ -25,7 +25,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.sketchy-code-rules-of-hooks.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md index 7ffe3f84cf..544532b3b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.store-property-in-global.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md index a88d43b352..f4d192530e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md @@ -19,7 +19,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `onClick` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md index 3d54bcd75c..6f9da8fda9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-after-loop-break.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md index c892066bf8..6ab4ecb36e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md @@ -27,7 +27,7 @@ error.unconditional-set-state-in-render-after-loop.ts:6:2 4 | for (const _ of props) { 5 | } > 6 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 7 | return state; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md index a617a2f572..69c81c3ff8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-with-loop-throw.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md index dfb5c7b56f..79369f3608 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md @@ -30,7 +30,7 @@ error.unconditional-set-state-lambda.ts:8:2 6 | setX(1); 7 | }; > 8 | foo(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 9 | 10 | return [x]; 11 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md index f03b514c3f..c8a775499a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md @@ -38,7 +38,7 @@ error.unconditional-set-state-nested-function-expressions.ts:16:2 14 | bar(); 15 | }; > 16 | baz(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 17 | 18 | return [x]; 19 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md index 8432be198b..cfd27c7356 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md @@ -23,13 +23,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `renderCount` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.update-global-should-bailout.ts:3:2 1 | let renderCount = 0; 2 | function useFoo() { > 3 | renderCount += 1; - | ^^^^^^^^^^^^^^^^ `renderCount` cannot be reassigned + | ^^^^^^^^^^^^^^^^ `renderCount` should not be reassigned 4 | return renderCount; 5 | } 6 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md index 188814ee02..1531425c20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md @@ -30,9 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Expected the dependency list for useMemo to be an array literal - -Expected the dependency list for useMemo to be an array literal +Error: Expected the dependency list of useMemo or useCallback to be an array literal error.useMemo-non-literal-depslist.ts:10:4 8 | return text.toUpperCase(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md index d8250c6f57..b4f642df43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Expected a single dynamic gating directive","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md index 08eb396bb1..cf7a8cad85 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr2` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":333},"end":{"line":11,"column":43,"index":357},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":248},"end":{"line":8,"column":46,"index":292},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":277},"end":{"line":8,"column":34,"index":280},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":316},"end":{"line":11,"column":54,"index":368},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":349},"end":{"line":11,"column":42,"index":356},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index b629bfef27..0925a9b982 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -65,7 +65,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index c16890e52e..3e3135929b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -45,7 +45,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md index a9782a3b9e..a6f268555c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index babb4e8969..96b1d866fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -44,7 +44,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md index d78e4becec..bef031723c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md @@ -35,12 +35,12 @@ Found 1 error: Error: Cannot access variable before it is declared -`data` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized 9 | // TDZ violation! 10 | const onRefetch = useCallback(() => { > 11 | refetch(data); - | ^^^^ `data` accessed before it is declared + | ^^^^ `data` is accessed before it is declared 12 | }, [refetch]); 13 | 14 | // The context variable gets frozen here since it's passed to a hook diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md index 80a12e5d40..f1cc5c4808 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md @@ -22,7 +22,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | function Component(props) { @@ -35,7 +35,7 @@ error.not-useEffect-external-mutate.ts:6:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:7:4 5 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md index 41ed513912..bb4d91a4bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md @@ -22,26 +22,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | const foo = () => { 4 | // Cannot assign to globals > 5 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 6 | moduleLocal = true; 7 | }; 8 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:6:4 4 | // Cannot assign to globals 5 | someUnknownGlobal = true; > 6 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 7 | }; 8 | foo(); 9 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md index 6089255fd5..84339b0759 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md @@ -19,26 +19,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | function Component() { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | } 7 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:5:2 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | } 7 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md index 2e0c890367..8441e79925 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr2` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":365},"end":{"line":11,"column":43,"index":389},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":280},"end":{"line":8,"column":46,"index":324},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":309},"end":{"line":8,"column":34,"index":312},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":348},"end":{"line":11,"column":54,"index":400},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":381},"end":{"line":11,"column":42,"index":388},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md index 27af59e175..f433f8cb88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md @@ -24,8 +24,6 @@ Found 1 error: Error: Expected the first argument to be an inline function expression -Expected the first argument to be an inline function expression - error.validate-useMemo-named-function.ts:9:20 7 | // for now. 8 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index 006d2a49c0..bda9c3b694 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -31,7 +31,7 @@ function Component({prop1}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:4) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index 8481ed2c57..9604f69476 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -15,7 +15,7 @@ console.log(fire == null); ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index f84686bc36..02753ce345 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -32,7 +32,7 @@ function Component({props, bar}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md index 81c36a362c..cdb0726f2e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md @@ -26,7 +26,7 @@ function Component({props, bar}) { ``` Found 2 errors: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. @@ -39,7 +39,7 @@ error.invalid-outside-effect.ts:8:2 10 | useCallback(() => { 11 | fire(foo(props)); -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md index 96cea9c08f..d82f1ee1b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md @@ -27,7 +27,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md index 4dc5336ebe..a841813630 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md @@ -30,7 +30,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts new file mode 100644 index 0000000000..c3d6704504 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts @@ -0,0 +1,32 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoImpureFunctionCallsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no impure function calls rule', NoImpureFunctionCallsRule, { + valid: [], + invalid: [ + { + name: 'Known impure function calls are caught', + code: normalizeIndent` + function Component() { + const date = Date.now(); + const now = performance.now(); + const rand = Math.random(); + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts new file mode 100644 index 0000000000..9192087b31 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts @@ -0,0 +1,85 @@ +/** + * 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 {RulesOfHooksRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('rules-of-hooks', RulesOfHooksRule, { + valid: [ + { + name: 'Basic example', + code: normalizeIndent` + function Component() { + useHook(); + return
Hello world
; + } + `, + }, + { + name: 'Violation with Flow suppression', + code: ` + // Valid since error already suppressed with flow. + function useHook() { + if (cond) { + // $FlowFixMe[react-rule-hook] + useConditionalHook(); + } + } + `, + }, + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function useConditional() { + cond ?? useConditionalHook(); + props.cond && useConditionalHook(); + return
Hello world
; + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts new file mode 100644 index 0000000000..8463e860df --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts @@ -0,0 +1,31 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoAmbiguousJsxRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ambiguous JSX rule', NoAmbiguousJsxRule, { + valid: [], + invalid: [ + { + name: 'JSX in try blocks are warned against', + code: normalizeIndent` + function Component(props) { + let el; + try { + el = ; + } catch { + return null; + } + return el; + } + `, + errors: [makeTestCaseError(ErrorCode.JSX_IN_TRY)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts new file mode 100644 index 0000000000..821cab8007 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {NoCapitalizedCallsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidCapitalizedCallsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('no-capitalized-calls', NoCapitalizedCallsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + import Child from './Child'; + function Component() { + return <> + {Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Method call violation', + code: normalizeIndent` + import myModule from './MyModule'; + function Component() { + return <> + {myModule.Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + import Child1 from './Child1'; + import MyModule from './MyModule'; + function Component() { + return <> + {Child1()} + {MyModule.Child2()} + ; + }`, + errors: [ + invalidCapitalizedCallsRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts new file mode 100644 index 0000000000..a8498d303f --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts @@ -0,0 +1,27 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoRefAccessInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ref access in render rule', NoRefAccessInRenderRule, { + valid: [], + invalid: [ + { + name: 'validate against simple ref access in render', + code: normalizeIndent` + function Component(props) { + const ref = useRef(null); + const value = ref.current; + return value; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_REF_ACCESS_IN_RENDER)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts new file mode 100644 index 0000000000..43ec6ad010 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts @@ -0,0 +1,48 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoSetStateInEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in effects rule', NoSetStateInEffectsRule, { + valid: [], + invalid: [ + { + name: 'unconditional setState in useEffect', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + setState(s => s + 1); + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + { + name: 'conditional setState in render', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + if (state % 2 === 0) { + setState(state + 1); + } + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts new file mode 100644 index 0000000000..77ce895339 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {ValidateSetStateInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in render rule', ValidateSetStateInRenderRule, { + valid: [], + invalid: [ + { + name: 'setState in useMemo', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + return item; + }, [cond, item, init]); + + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'unconditional setState in render', + code: normalizeIndent` + function Component(props) { + const [x, setX] = useState(0); + const aliased = setX; + + setX(1); + aliased(2); + + return x; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts new file mode 100644 index 0000000000..77f6dd93fb --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {NoUnusedDirectivesRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule} from './shared-utils'; + +testRule('no unused directives rule', NoUnusedDirectivesRule, { + valid: [], + invalid: [ + { + name: "Unused 'use no forget' directive is reported when no errors are present on components", + code: normalizeIndent` + function Component() { + 'use no forget'; + return
Hello world
+ } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction Component() {\n \n return
Hello world
\n}\n', + }, + ], + }, + ], + }, + + { + name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", + code: normalizeIndent` + function notacomponent() { + 'use no forget'; + return 1 + 1; + } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', + }, + ], + }, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts new file mode 100644 index 0000000000..c0e0cf07c9 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts @@ -0,0 +1,172 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + testRule, + makeTestCaseError, + TestRecommendedRules, +} from './shared-utils'; +import {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; + +testRule('plugin-recommended', TestRecommendedRules, { + valid: [ + { + name: 'Basic example with component syntax', + code: normalizeIndent` + export default component HelloWorld( + text: string = 'Hello!', + onClick: () => void, + ) { + return
{text}
; + } + `, + }, + + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Multiple diagnostic kinds from the same function are surfaced', + code: normalizeIndent` + import Child from './Child'; + function Component() { + const result = cond ?? useConditionalHook(); + return <> + {Child(result)} + ; + } + `, + errors: [ + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + { + name: 'Multiple diagnostics within the same file are surfaced', + code: normalizeIndent` + function useConditional1() { + 'use memo'; + return cond ?? useConditionalHook(); + } + function useConditional2(props) { + 'use memo'; + return props.cond && useConditionalHook(); + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + { + name: "'use no forget' does not disable eslint rule", + code: normalizeIndent` + let count = 0; + function Component() { + 'use no forget'; + return cond ?? useConditionalHook(); + + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple non-fatal useMemo diagnostics are surfaced', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + }, [cond, item, init]); + + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'Pipeline errors are reported', + code: normalizeIndent` + import useMyEffect from 'useMyEffect'; + import {AUTODEPS} from 'react'; + function Component({a}) { + 'use no memo'; + useMyEffect(() => console.log(a.b), AUTODEPS); + return
Hello world
; + } + `, + options: [ + { + environment: { + inferEffectDependencies: [ + { + function: { + source: 'useMyEffect', + importSpecifierName: 'default', + }, + autodepsIndex: 1, + }, + ], + }, + }, + ], + errors: [ + { + message: /Cannot infer dependencies of this effect/, + }, + ], + }, + ], +}); + +testRule('rules that are not enabled do not error', StaticComponentsRule, { + valid: [ + { + name: 'simple case', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + }, + ], + invalid: [], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts deleted file mode 100644 index bff40e9649..0000000000 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts +++ /dev/null @@ -1,287 +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 {ErrorSeverity} from 'babel-plugin-react-compiler/src'; -import {RuleTester as ESLintTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)![0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: ESLintTester.ValidTestCase[]; - invalid: ESLintTester.InvalidTestCase[]; -}; - -const tests: CompilerTestCases = { - valid: [ - { - name: 'Basic example', - code: normalizeIndent` - function foo(x, y) { - if (x) { - return foo(false, y); - } - return [y * 10]; - } - `, - }, - { - name: 'Violation with Flow suppression', - code: ` - // Valid since error already suppressed with flow. - function useHookWithHook() { - if (cond) { - // $FlowFixMe[react-rule-hook] - useConditionalHook(); - } - } - `, - }, - { - name: 'Basic example with component syntax', - code: normalizeIndent` - export default component HelloWorld( - text: string = 'Hello!', - onClick: () => void, - ) { - return
{text}
; - } - `, - }, - { - name: 'Unsupported syntax', - code: normalizeIndent` - function foo(x) { - var y = 1; - return y * x; - } - `, - }, - { - // OK because invariants are only meant for the compiler team's consumption - name: '[Invariant] Defined after use', - code: normalizeIndent` - function Component(props) { - let y = function () { - m(x); - }; - - let x = { a }; - m(x); - return y; - } - `, - }, - { - name: "Classes don't throw", - code: normalizeIndent` - class Foo { - #bar() {} - } - `, - }, - ], - invalid: [ - { - name: 'Reportable levels can be configured', - options: [{reportableLevels: new Set([ErrorSeverity.Todo])}], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: '[InvalidReact] ESlint suppression', - // Indentation is intentionally weird so it doesn't add extra whitespace - code: normalizeIndent` - function Component(props) { - // eslint-disable-next-line react-hooks/rules-of-hooks - return
{props.foo}
; - }`, - errors: [ - { - message: /React Compiler has skipped optimizing this component/, - suggestions: [ - { - output: normalizeIndent` - function Component(props) { - - return
{props.foo}
; - }`, - }, - ], - }, - { - message: - "Definition for rule 'react-hooks/rules-of-hooks' was not found.", - }, - ], - }, - { - name: 'Multiple diagnostics are surfaced', - options: [ - { - reportableLevels: new Set([ - ErrorSeverity.Todo, - ErrorSeverity.InvalidReact, - ]), - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - } - function Bar(props) { - props.a.b = 2; - return
{props.c}
- }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - { - message: /Modifying component props or hook arguments is not allowed/, - }, - ], - }, - { - name: 'Test experimental/unstable report all bailouts mode', - options: [ - { - reportableLevels: new Set([ErrorSeverity.InvalidReact]), - __unstable_donotuse_reportAllBailouts: true, - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: "'use no forget' does not disable eslint rule", - code: normalizeIndent` - let count = 0; - function Component() { - 'use no forget'; - count = count + 1; - return
Hello world {count}
- } - `, - errors: [ - { - message: - /Cannot reassign variables declared outside of the component\/hook/, - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on components", - code: normalizeIndent` - function Component() { - 'use no forget'; - return
Hello world
- } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction Component() {\n \n return
Hello world
\n}\n', - }, - ], - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", - code: normalizeIndent` - function notacomponent() { - 'use no forget'; - return 1 + 1; - } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', - }, - ], - }, - ], - }, - { - name: 'Pipeline errors are reported', - code: normalizeIndent` - import useMyEffect from 'useMyEffect'; - import {AUTODEPS} from 'react'; - function Component({a}) { - 'use no memo'; - useMyEffect(() => console.log(a.b), AUTODEPS); - return
Hello world
; - } - `, - options: [ - { - environment: { - inferEffectDependencies: [ - { - function: { - source: 'useMyEffect', - importSpecifierName: 'default', - }, - autodepsIndex: 1, - }, - ], - }, - }, - ], - errors: [ - { - message: /Cannot infer dependencies of this effect/, - }, - ], - }, - ], -}; - -const eslintTester = new ESLintTester({ - parser: require.resolve('hermes-eslint'), - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, -}); -eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts index 5a2bea6852..87baf724e1 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts @@ -6,22 +6,11 @@ */ import {RuleTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)[0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: RuleTester.ValidTestCase[]; - invalid: RuleTester.InvalidTestCase[]; -}; +import { + CompilerTestCases, + normalizeIndent, + TestRecommendedRules, +} from './shared-utils'; const tests: CompilerTestCases = { valid: [ @@ -70,6 +59,7 @@ const tests: CompilerTestCases = { }; const eslintTester = new RuleTester({ + // @ts-ignore[2353] - outdated types parser: require.resolve('@typescript-eslint/parser'), }); -eslintTester.run('react-compiler', ReactCompilerRule, tests); +eslintTester.run('react-compiler', TestRecommendedRules, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts new file mode 100644 index 0000000000..b2f4b54994 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts @@ -0,0 +1,44 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidStaticComponentsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('static-components', StaticComponentsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function Example(props) { + const Component = new ComponentFactory(); + return ; + } + `, + errors: [invalidStaticComponentsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function Example(props) { + const Component1 = new ComponentFactory(); + const Component2 = new ComponentFactory(); + return <> + + + ; + }`, + errors: [ + invalidStaticComponentsRuleErrorMessage, + invalidStaticComponentsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts new file mode 100644 index 0000000000..8d7f481f15 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts @@ -0,0 +1,36 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {UnnecessaryEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('unnecessary effects rule', UnnecessaryEffectsRule, { + valid: [], + invalid: [ + { + name: 'test case from React docs', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + // https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state + function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + + // 🔴 Avoid: redundant state and unnecessary Effect + const [fullName, setFullName] = useState(''); + useEffect(() => { + setFullName(capitalize(firstName + ' ' + lastName)); + }, [firstName, lastName]); + + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts new file mode 100644 index 0000000000..85c937a2de --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts @@ -0,0 +1,43 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {UseMemoRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('use memo rule', UseMemoRule, { + valid: [], + invalid: [ + { + name: 'Simple async violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component({a, b}) { + let x = useMemo(async () => { + await a; + }, []); + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC)], + }, + { + name: 'Simple parameters violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component() { + let x = useMemo(c => a, []); + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts new file mode 100644 index 0000000000..f9553730e4 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts @@ -0,0 +1,98 @@ +import {RuleTester as ESLintTester, Rule} from 'eslint'; +import { + ErrorCode, + ErrorCodeDetails, +} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import escape from 'regexp.escape'; +import {configs} from '../src/index'; + +/** + * A string template tag that removes padding from the left side of multi-line strings + * @param {Array} strings array of code strings (only one expected) + */ +export function normalizeIndent(strings: TemplateStringsArray): string { + const codeLines = strings[0].split('\n'); + const leftPadding = codeLines[1].match(/\s+/)![0]; + return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); +} + +export type CompilerTestCases = { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; +}; + +export const invalidHookRuleErrorMessage: ESLintTester.TestCaseError = { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason), + ), +}; + +export const invalidCapitalizedCallsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.CAPITALIZED_CALLS].reason), + ), + }; + +export const invalidStaticComponentsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.STATIC_COMPONENTS].reason), + ), + }; + +export function makeTestCaseError(code: ErrorCode): ESLintTester.TestCaseError { + return { + message: new RegExp(escape(ErrorCodeDetails[code].reason)), + }; +} + +export function testRule( + name: string, + rule: Rule.RuleModule, + tests: { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; + }, +): void { + const eslintTester = new ESLintTester({ + // @ts-ignore[2353] - outdated types + parser: require.resolve('hermes-eslint'), + parserOptions: { + ecmaVersion: 2015, + sourceType: 'module', + enableExperimentalComponentSyntax: true, + }, + }); + + eslintTester.run(name, rule, tests); +} + +/** + * Aggregates all recommended rules from the plugin. + */ +export const TestRecommendedRules: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Disallow capitalized function calls', + category: 'Possible Errors', + recommended: true, + }, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context) { + for (const rule of Object.values( + configs.recommended.plugins['react-compiler'].rules, + )) { + const listener = rule.create(context); + if (Object.entries(listener).length !== 0) { + throw new Error('TODO: handle rules that return listeners to eslint'); + } + } + return {}; + }, +}; + +test('no test', () => {}); diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index 2dd191f033..e5402611e2 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -24,11 +24,13 @@ "@babel/preset-typescript": "^7.18.6", "@babel/types": "^7.26.0", "@types/eslint": "^8.56.12", + "@types/jest": "^30.0.0", "@types/node": "^20.2.5", "babel-jest": "^29.0.3", "eslint": "8.57.0", "hermes-eslint": "^0.25.1", - "jest": "^29.5.0" + "jest": "^29.5.0", + "regexp.escape": "^2.0.1" }, "engines": { "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" diff --git a/compiler/packages/eslint-plugin-react-compiler/src/index.ts b/compiler/packages/eslint-plugin-react-compiler/src/index.ts index a3577a101e..681e9844ef 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/index.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/index.ts @@ -5,29 +5,61 @@ * LICENSE file in the root directory of this source tree. */ -import ReactCompilerRule from './rules/ReactCompilerRule'; +import * as rules from './rules/ReactCompilerRule'; const meta = { name: 'eslint-plugin-react-compiler', }; -const rules = { - 'react-compiler': ReactCompilerRule, +/** + * Validates React components and hooks follow rules of React + * and follow best practices + */ +const validationRules = { + 'rules-of-hooks': rules.RulesOfHooksRule, + 'no-capitalized-calls': rules.NoCapitalizedCallsRule, + 'no-unstable-components': rules.StaticComponentsRule, + 'validate-use-memo-callbacks': rules.UseMemoRule, + 'validate-writes': rules.InvalidWritesRule, + 'no-unsafe-refs': rules.UnsafeRefsRule, + 'validate-set-state-in-render': rules.ValidateSetStateInRenderRule, + 'no-set-state-in-effects': rules.NoSetStateInEffectsRule, + 'no-ref-access-in-render': rules.NoRefAccessInRenderRule, + 'no-impure-function-calls': rules.NoImpureFunctionCallsRule, + 'unnecessary-effects': rules.UnnecessaryEffectsRule, + 'no-ambiguous-jsx': rules.NoAmbiguousJsxRule, +}; + +const recommendedRules = { + ...validationRules, + 'no-unsupported-syntax': rules.NoUnsupportedSyntaxRule, + + /** Validation for React Compiler inline configuration */ + 'no-unused-directives': rules.NoUnusedDirectivesRule, + 'validate-compiler-config': rules.ValidateCompilerConfigRule, +}; + +const allRules = { + ...recommendedRules, + /** Warn on syntax that React Compiler cannot transform */ + 'no-todo-syntax': rules.WarnOnTodoSyntaxRule, + 'warn-on-compilation-failures': rules.WarnOnUnactionableFailuresRule, }; const configs = { recommended: { plugins: { 'react-compiler': { - rules: { - 'react-compiler': ReactCompilerRule, - }, + rules: recommendedRules, }, }, - rules: { - 'react-compiler/react-compiler': 'error' as const, - }, + rules: Object.fromEntries( + Object.keys(recommendedRules).map(ruleName => [ + 'react-compiler/' + ruleName, + 'error', + ]), + ) as Record, }, }; -export {configs, rules, meta}; +export {configs, allRules as rules, meta}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index 730b6ff6f8..0058464090 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -5,43 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import {transformFromAstSync} from '@babel/core'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import type {SourceLocation as BabelSourceLocation} from '@babel/types'; -import BabelPluginReactCompiler, { - CompilerDiagnostic, +import { CompilerDiagnosticOptions, - CompilerErrorDetail, CompilerErrorDetailOptions, CompilerSuggestionOperation, - ErrorSeverity, - parsePluginOptions, - validateEnvironmentConfig, - OPT_OUT_DIRECTIVES, - type PluginOptions, } from 'babel-plugin-react-compiler/src'; -import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Rule} from 'eslint'; -import {Statement} from 'estree'; -import * as HermesParser from 'hermes-parser'; +import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler'; +import {LinterCategory} from 'babel-plugin-react-compiler/src/CompilerError'; function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } -const DEFAULT_REPORTABLE_LEVELS = new Set([ - ErrorSeverity.InvalidReact, - ErrorSeverity.InvalidJS, -]); -let reportableLevels = DEFAULT_REPORTABLE_LEVELS; - -function isReportableDiagnostic( - detail: CompilerErrorDetail | CompilerDiagnostic, -): boolean { - return reportableLevels.has(detail.severity); -} - function makeSuggestions( detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array { @@ -95,28 +72,97 @@ function makeSuggestions( return suggest; } -const COMPILER_OPTIONS: Partial = { - noEmit: true, - panicThreshold: 'none', - // Don't emit errors on Flow suppressions--Flow already gave a signal - flowSuppressions: false, - environment: validateEnvironmentConfig({ - validateRefAccessDuringRender: true, - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - }), -}; +function getReactCompilerResult(context: Rule.RuleContext): RunCacheEntry { + // Compat with older versions of eslint + const sourceCode = context.sourceCode ?? context.getSourceCode(); + const filename = context.filename ?? context.getFilename(); + const userOpts = context.options[0] ?? {}; -const rule: Rule.RuleModule = { + const results = runReactCompiler({ + sourceCode, + filename, + userOpts, + }); + + return results; +} + +function hasFlowSuppression( + program: RunCacheEntry, + nodeLoc: BabelSourceLocation, + suppressions: Array, +): boolean { + for (const commentNode of program.flowSuppressions) { + if ( + suppressions.includes(commentNode.code) && + commentNode.line === nodeLoc.start.line - 1 + ) { + return true; + } + } + return false; +} + +function makeRule(filter: Array): Rule.RuleModule['create'] { + return (context: Rule.RuleContext): Rule.RuleListener => { + const result = getReactCompilerResult(context); + + for (const event of result.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if ( + detail.linterCategory != null && + filter.includes(detail.linterCategory) + ) { + const loc = detail.primaryLocation(); + if (loc == null || typeof loc === 'symbol') { + continue; + } + if ( + hasFlowSuppression(result, loc, [ + 'react-rule-hook', + 'react-rule-unsafe-ref', + ]) + ) { + // If Flow already caught this error, we don't need to report it again. + continue; + } + // TODO: if multiple rules report the same linter category, + // we should deduplicate them with a "reported" set + context.report({ + message: detail.printErrorMessage(result.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); + } + } + } + return {}; + }; +} + +const unactionableBailouts: Rule.RuleModule = { meta: { type: 'problem', docs: { - description: 'Surfaces diagnostics from React Forget', + description: 'Surfaces unactionable compilation bailouts', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + }, + create(context: Rule.RuleContext): Rule.RuleListener { + return {}; + }, +}; + +export const RulesOfHooksRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to the rules of hooks', recommended: true, }, fixable: 'code', @@ -124,234 +170,284 @@ const rule: Rule.RuleModule = { // validation is done at runtime with zod schema: [{type: 'object', additionalProperties: true}], }, - create(context: Rule.RuleContext) { - // Compat with older versions of eslint - const sourceCode = context.sourceCode ?? context.getSourceCode(); - const filename = context.filename ?? context.getFilename(); - const userOpts = context.options[0] ?? {}; - if ( - userOpts.reportableLevels != null && - userOpts.reportableLevels instanceof Set - ) { - reportableLevels = userOpts.reportableLevels; - } else { - reportableLevels = DEFAULT_REPORTABLE_LEVELS; - } - /** - * Experimental setting to report all compilation bailouts on the compilation - * unit (e.g. function or hook) instead of the offensive line. - * Intended to be used when a codebase is 100% reliant on the compiler for - * memoization (i.e. deleted all manual memo) and needs compilation success - * signals for perf debugging. - */ - let __unstable_donotuse_reportAllBailouts: boolean = false; - if ( - userOpts.__unstable_donotuse_reportAllBailouts != null && - typeof userOpts.__unstable_donotuse_reportAllBailouts === 'boolean' - ) { - __unstable_donotuse_reportAllBailouts = - userOpts.__unstable_donotuse_reportAllBailouts; - } + create: makeRule([LinterCategory.RULES_OF_HOOKS]), +}; - let shouldReportUnusedOptOutDirective = true; - const options: PluginOptions = parsePluginOptions({ - ...COMPILER_OPTIONS, - ...userOpts, - environment: { - ...COMPILER_OPTIONS.environment, - ...userOpts.environment, - }, - }); - const userLogger: Logger | null = options.logger; - options.logger = { - logEvent: (eventFilename, event): void => { - userLogger?.logEvent(eventFilename, event); - if (event.kind === 'CompileError') { - shouldReportUnusedOptOutDirective = false; - const detail = event.detail; - const suggest = makeSuggestions(detail.options); - if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) { - const loc = detail.primaryLocation(); - const locStr = - loc != null && typeof loc !== 'symbol' - ? ` (@:${loc.start.line}:${loc.start.column})` - : ''; - /** - * Report bailouts with a smaller span (just the first line). - * Compiler bailout lints only serve to flag that a react function - * has not been optimized by the compiler for codebases which depend - * on compiler memo heavily for perf. These lints are also often not - * actionable. - */ - let endLoc; - if (event.fnLoc.end.line === event.fnLoc.start.line) { - endLoc = event.fnLoc.end; - } else { - endLoc = { - line: event.fnLoc.start.line, - // Babel loc line numbers are 1-indexed - column: - sourceCode.text.split(/\r?\n|\r|\n/g)[ - event.fnLoc.start.line - 1 - ]?.length ?? 0, - }; - } - const firstLineLoc = { - start: event.fnLoc.start, - end: endLoc, - }; - context.report({ - message: `${detail.printErrorMessage(sourceCode.text, {eslint: true})} ${locStr}`, - loc: firstLineLoc, - suggest, - }); - } +export const NoCapitalizedCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to capitalized calls', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.CAPITALIZED_CALLS]), +}; +export const StaticComponentsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'todo', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.STATIC_COMPONENTS]), +}; + +export const InvalidWritesRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.INVALID_WRITE]), +}; +export const UseMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Surfaces compilation errors related to invalid useMemo usage', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.VALIDATE_MANUAL_MEMO]), +}; + +// TODO: test cases +export const NoDynamicManualMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.DYNAMIC_MANUAL_MEMO]), +}; + +export const UnsafeRefsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to unsafe refs', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.EXHAUSTIVE_DEPS]), +}; + +export const ValidateSetStateInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_RENDER]), +}; + +export const NoSetStateInEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to setState in render', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_EFFECTS]), +}; + +export const NoRefAccessInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_REF_ACCESS_IN_RENDER]), +}; + +export const NoImpureFunctionCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.IMPURE_FUNCTIONS]), +}; + +export const UnnecessaryEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNNECESSARY_EFFECTS]), +}; + +export const NoAmbiguousJsxRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on JSX usage that is ambiguous.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.JSX_IN_TRY]), +}; + +// TODO: test cases +export const NoUnsupportedSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler does not and will not support.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNSUPPORTED_SYNTAX]), +}; + +// TODO: test cases +export const WarnOnTodoSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler currently does not support, but may in the future.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.TODO_SYNTAX]), +}; + +// TODO: test cases +export const ValidateCompilerConfigRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Validates the React Compiler configuration', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.COMPILER_CONFIG]), +}; + +export const WarnOnUnactionableFailuresRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on compilation failures that are not actionable', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const event of results.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if (detail.linterCategory == null) { const loc = detail.primaryLocation(); - if ( - !isReportableDiagnostic(detail) || - loc == null || - typeof loc === 'symbol' - ) { - return; + if (loc == null || typeof loc === 'symbol') { + continue; } - if ( - hasFlowSuppression(loc, 'react-rule-hook') || - hasFlowSuppression(loc, 'react-rule-unsafe-ref') - ) { - // If Flow already caught this error, we don't need to report it again. - return; - } - if (loc != null) { - context.report({ - message: detail.printErrorMessage(sourceCode.text, { - eslint: true, - }), - loc, - suggest, - }); - } - } - }, - }; - - try { - options.environment = validateEnvironmentConfig( - options.environment ?? {}, - ); - } catch (err: unknown) { - options.logger?.logEvent('', err as LoggerEvent); - } - - function hasFlowSuppression( - nodeLoc: BabelSourceLocation, - suppression: string, - ): boolean { - const comments = sourceCode.getAllComments(); - const flowSuppressionRegex = new RegExp( - '\\$FlowFixMe\\[' + suppression + '\\]', - ); - for (const commentNode of comments) { - if ( - flowSuppressionRegex.test(commentNode.value) && - commentNode.loc!.end.line === nodeLoc.start.line - 1 - ) { - return true; + context.report({ + message: detail.printErrorMessage(results.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); } } - return false; - } - - let babelAST; - if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { - try { - const {parse: babelParse} = require('@babel/parser'); - babelAST = babelParse(sourceCode.text, { - filename, - sourceType: 'unambiguous', - plugins: ['typescript', 'jsx'], - }); - } catch { - /* empty */ - } - } else { - try { - babelAST = HermesParser.parse(sourceCode.text, { - babel: true, - enableExperimentalComponentSyntax: true, - sourceFilename: filename, - sourceType: 'module', - }); - } catch { - /* empty */ - } - } - - if (babelAST != null) { - try { - transformFromAstSync(babelAST, sourceCode.text, { - filename, - highlightCode: false, - retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], - sourceType: 'module', - configFile: false, - babelrc: false, - }); - } catch (err) { - /* errors handled by injected logger */ - } - } - - function reportUnusedOptOutDirective(stmt: Statement) { - if ( - stmt.type === 'ExpressionStatement' && - stmt.expression.type === 'Literal' && - typeof stmt.expression.value === 'string' && - OPT_OUT_DIRECTIVES.has(stmt.expression.value) && - stmt.loc != null - ) { - context.report({ - message: `Unused '${stmt.expression.value}' directive`, - loc: stmt.loc, - suggest: [ - { - desc: 'Remove the directive', - fix(fixer) { - return fixer.remove(stmt); - }, - }, - ], - }); - } - } - if (shouldReportUnusedOptOutDirective) { - return { - FunctionDeclaration(fnDecl) { - for (const stmt of fnDecl.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - ArrowFunctionExpression(fnExpr) { - if (fnExpr.body.type === 'BlockStatement') { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - } - }, - FunctionExpression(fnExpr) { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - }; - } else { - return {}; } + return {}; }, }; -export default rule; +export const NoUnusedDirectivesRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const directive of results.unusedOptOutDirectives) { + context.report({ + message: `Unused '${directive.directive}' directive`, + loc: directive.loc, + suggest: [ + { + desc: 'Remove the directive', + fix(fixer) { + return fixer.removeRange(directive.range); + }, + }, + ], + }); + } + return {}; + }, +}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts new file mode 100644 index 0000000000..655eaf5197 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -0,0 +1,323 @@ +/** + * 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 {transformFromAstSync, traverse} from '@babel/core'; +import {parse as babelParse} from '@babel/parser'; +import {Directive, File} from '@babel/types'; +// @ts-expect-error: no types available +import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; +import BabelPluginReactCompiler, { + parsePluginOptions, + validateEnvironmentConfig, + OPT_OUT_DIRECTIVES, + type PluginOptions, +} from 'babel-plugin-react-compiler/src'; +import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; +import type {SourceCode} from 'eslint'; +import {SourceLocation} from 'estree'; +// @ts-expect-error: no types available +import * as HermesParser from 'hermes-parser'; +import {isDeepStrictEqual} from 'util'; +import type {ParseResult} from '@babel/parser'; + +const COMPILER_OPTIONS: Partial = { + noEmit: true, + panicThreshold: 'none', + // Don't emit errors on Flow suppressions--Flow already gave a signal + flowSuppressions: false, + environment: validateEnvironmentConfig({ + validateRefAccessDuringRender: true, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + // TODO: remove, this should be in the type system + validateNoCapitalizedCalls: [], + validateHooksUsage: true, + validateNoDerivedComputationsInEffects: true, + }), +}; + +export type UnusedOptOutDirective = { + loc: SourceLocation; + range: [number, number]; + directive: string; +}; +export type RunCacheEntry = { + sourceCode: string; + filename: string; + userOpts: PluginOptions; + flowSuppressions: Array<{line: number; code: string}>; + unusedOptOutDirectives: Array; + events: LoggerEvent[]; +}; + +type RunParams = { + sourceCode: SourceCode; + filename: string; + userOpts: PluginOptions; +}; +const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g; + +function getFlowSuppressions( + sourceCode: SourceCode, +): Array<{line: number; code: string}> { + const comments = sourceCode.getAllComments(); + const results: Array<{line: number; code: string}> = []; + + for (const commentNode of comments) { + const matches = commentNode.value.matchAll(FLOW_SUPPRESSION_REGEX); + for (const match of matches) { + if (match.index != null && commentNode.loc != null) { + const code = match[1]; + results.push({ + line: commentNode.loc!.end.line, + code, + }); + } + } + } + return results; +} + +function filterUnusedOptOutDirectives( + directives: ReadonlyArray, +): Array { + const results: Array = []; + for (const directive of directives) { + if ( + OPT_OUT_DIRECTIVES.has(directive.value.value) && + directive.loc != null + ) { + results.push({ + loc: directive.loc, + directive: directive.value.value, + range: [directive.start!, directive.end!], + }); + } + } + return results; +} + +function runReactCompilerImpl({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + // Compat with older versions of eslint + for (const [key, entry] of Object.entries(userOpts)) { + if (key === 'environment' && COMPILER_OPTIONS.environment != null) { + for (const envKey of Object.keys(entry as Record)) { + if ( + COMPILER_OPTIONS.environment.hasOwnProperty(envKey) && + isDeepStrictEqual( + (entry as Record)[envKey], + (COMPILER_OPTIONS.environment as Record)[envKey], + ) + ) { + console.warn('Conflicting environment option detected: ' + envKey); + } + } + } else if (COMPILER_OPTIONS.hasOwnProperty(key)) { + if (isDeepStrictEqual(entry, (COMPILER_OPTIONS as any)[key])) { + console.warn('Conflicting option detected: ' + key); + } + } + } + const options: PluginOptions = parsePluginOptions({ + ...COMPILER_OPTIONS, + ...userOpts, + environment: { + ...COMPILER_OPTIONS.environment, + ...userOpts.environment, + }, + }); + const results: RunCacheEntry = { + sourceCode: sourceCode.text, + filename, + userOpts, + flowSuppressions: [], + unusedOptOutDirectives: [], + events: [], + }; + const userLogger: Logger | null = options.logger; + options.logger = { + logEvent: (eventFilename, event): void => { + userLogger?.logEvent(eventFilename, event); + results.events.push(event); + }, + }; + + try { + options.environment = validateEnvironmentConfig(options.environment ?? {}); + } catch (err: unknown) { + options.logger?.logEvent(filename, err as LoggerEvent); + } + + let babelAST: ParseResult | null = null; + if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { + try { + babelAST = babelParse(sourceCode.text, { + sourceFilename: filename, + sourceType: 'unambiguous', + plugins: ['typescript', 'jsx'], + }); + } catch { + /* empty */ + } + } else { + try { + babelAST = HermesParser.parse(sourceCode.text, { + babel: true, + enableExperimentalComponentSyntax: true, + sourceFilename: filename, + sourceType: 'module', + }); + } catch { + /* empty */ + } + } + + if (babelAST != null) { + results.flowSuppressions = getFlowSuppressions(sourceCode); + try { + transformFromAstSync(babelAST, sourceCode.text, { + filename, + highlightCode: false, + retainLines: true, + plugins: [ + [PluginProposalPrivateMethods, {loose: true}], + [BabelPluginReactCompiler, options], + ], + sourceType: 'module', + configFile: false, + babelrc: false, + }); + + if (results.events.filter(e => e.kind === 'CompileError').length === 0) { + traverse(babelAST, { + FunctionDeclaration(path) { + path.node; + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + ArrowFunctionExpression(path) { + if (path.node.body.type === 'BlockStatement') { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + } + }, + FunctionExpression(path) { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + }); + } + } catch (err) { + /* errors handled by injected logger */ + } + } + + return results; +} + +const SENTINEL = Symbol(); + +type T = {[k: string]: any}; + +// Array backed LRU cache -- should be small < 10 elements +class LRUCache { + // newest at headIdx, then headIdx + 1, ..., tailIdx + #values: Array<[K, T | Error] | [typeof SENTINEL, void]>; + #headIdx: number = 0; + // #store: (key: K) => T | Error; + // #isValue: null | ((key: T | Error) => key is T); + + constructor( + size: number, + // store: (key: K) => T | Error, + // isValue?: (key: T | Error) => key is T, + ) { + this.#values = new Array(size).fill(SENTINEL); + // this.#store = store; + // this.#isValue = isValue ?? null; + } + + // gets a value and sets it as "recently used" + get(key: K): T | null { + let idx = this.#values.findIndex(entry => entry[0] === key); + // If found, move to front + if (idx === this.#headIdx) { + return this.#values[this.#headIdx][1] as T; + } else if (idx < 0) { + return null; + // const value = this.#store(key); + // if (this.#isValue && !this.#isValue(value)) { + // return value; // Return error directly + // } + // this.#headIdx = + // (this.#headIdx - 1 + this.#values.length) % this.#values.length; + // this.#values[this.#headIdx] = [key, value]; + } + + const entry: [K, T] = this.#values[idx] as [K, T]; + + const len = this.#values.length; + for (let i = 0; i < Math.min(idx, len - 1); i++) { + this.#values[(this.#headIdx + i + 1) % len] = + this.#values[(this.#headIdx + i) % len]; + } + this.#values[this.#headIdx] = entry; + return entry[1]; + } + push(key: K, value: T): void { + this.#headIdx = + (this.#headIdx - 1 + this.#values.length) % this.#values.length; + this.#values[this.#headIdx] = [key, value]; + } +} +const cache = new LRUCache(10); + +export default function runReactCompiler({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + const entry = cache.get(filename); + if ( + entry != null && + entry.sourceCode === sourceCode.text && + isDeepStrictEqual(entry.userOpts, userOpts) + ) { + return entry; + } else if (entry != null) { + if (process.env['DEBUG']) { + console.log( + `Cache hit for ${filename}, but source code or options changed, recomputing`, + ); + } + } + + const runEntry = runReactCompilerImpl({ + sourceCode, + filename, + userOpts, + }); + // If we have a cache entry, we can update it + if (entry != null) { + Object.assign(entry, runEntry); + } else { + cache.push(filename, runEntry); + } + return {...runEntry}; +} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index a159359773..a6041bd5cc 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -338,7 +338,16 @@ export async function transformFixtureInput( if (logs.length !== 0) { formattedLogs = logs .map(({event}) => { - return JSON.stringify(event); + return JSON.stringify(event, (key, value) => { + if ( + key === 'detail' && + value != null && + typeof value.serialize === 'function' + ) { + return value.serialize(); + } + return value; + }); }) .join('\n'); } diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 6e1bc7feeb..696261cbf5 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -542,11 +542,6 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - "@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" @@ -1605,7 +1600,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": +"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.27.0", "@babel/types@^7.27.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== @@ -1613,14 +1608,6 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -2148,6 +2135,11 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/diff-sequences@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + "@jest/environment@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz" @@ -2178,6 +2170,13 @@ "@types/node" "*" jest-mock "^29.5.0" +"@jest/expect-utils@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz#9d42e4b8bc80367db30abc6c42b2cb14073f66fc" + integrity sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew== + dependencies: + "@jest/get-type" "30.0.1" + "@jest/expect-utils@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz" @@ -2274,6 +2273,11 @@ jest-mock "^29.5.0" jest-util "^29.5.0" +"@jest/get-type@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.0.1.tgz#0d32f1bbfba511948ad247ab01b9007724fc9f52" + integrity sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw== + "@jest/globals@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz" @@ -2313,6 +2317,14 @@ "@jest/types" "^29.6.3" jest-mock "^29.7.0" +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + "@jest/reporters@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz" @@ -2435,6 +2447,13 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/schemas@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" @@ -2663,6 +2682,19 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz" @@ -2965,6 +2997,11 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sinclair/typebox@^0.34.0": + version "0.34.38" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.38.tgz#2365df7c23406a4d79413a766567bfbca708b49d" + integrity sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" @@ -3154,6 +3191,11 @@ resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== +"@types/istanbul-lib-coverage@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + "@types/istanbul-lib-report@*": version "3.0.0" resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" @@ -3176,6 +3218,13 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jest@^28.1.6": version "28.1.8" resolved "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz" @@ -3200,6 +3249,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + "@types/jsdom@^20.0.0": version "20.0.0" resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz" @@ -3282,6 +3339,11 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz" @@ -3309,6 +3371,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.33": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz" @@ -3740,7 +3809,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: +ansi-styles@^5.0.0, ansi-styles@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== @@ -3803,11 +3872,32 @@ aria-query@^5.0.0: resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.0.2.tgz" integrity sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q== +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + ast-types@^0.13.4: version "0.13.4" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz" @@ -3815,6 +3905,11 @@ ast-types@^0.13.4: dependencies: tslib "^2.0.1" +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + async@^3.2.3: version "3.2.6" resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" @@ -3825,6 +3920,13 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + axios@^1.6.1: version "1.7.4" resolved "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz" @@ -4245,7 +4347,7 @@ cac@^6.7.14: resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== @@ -4253,7 +4355,17 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -call-bound@^1.0.2: +call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== @@ -4295,7 +4407,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: +chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4377,6 +4489,11 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz" integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== +ci-info@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" @@ -4722,6 +4839,33 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + date-fns@^2.29.1: version "2.30.0" resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz" @@ -4788,6 +4932,24 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + degenerator@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz" @@ -4922,7 +5084,7 @@ dreamopt@~0.6.0: dependencies: wordwrap ">=0.0.2" -dunder-proto@^1.0.1: +dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== @@ -5033,7 +5195,67 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.1: +es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.9: + version "1.24.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" + integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== @@ -5050,6 +5272,25 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" + es5-ext@0.8.x: version "0.8.2" resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz" @@ -5428,6 +5669,18 @@ expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expect@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.0.5.tgz#c23bf193c5e422a742bfd2990ad990811de41a5a" + integrity sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ== + dependencies: + "@jest/expect-utils" "30.0.5" + "@jest/get-type" "30.0.1" + jest-matcher-utils "30.0.5" + jest-message-util "30.0.5" + jest-mock "30.0.5" + jest-util "30.0.5" + express-rate-limit@^7.5.0: version "7.5.0" resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz" @@ -5719,6 +5972,13 @@ follow-redirects@^1.15.6: resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz" @@ -5788,6 +6048,23 @@ function-bind@^1.1.2: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -5798,7 +6075,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -5819,7 +6096,7 @@ get-package-type@^0.1.0: resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proto@^1.0.1: +get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -5839,6 +6116,15 @@ get-stream@^6.0.0: resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + get-uri@^6.0.1: version "6.0.4" resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz" @@ -5957,6 +6243,14 @@ globals@^14.0.0: resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" @@ -5969,12 +6263,12 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.2.0: +gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -5989,6 +6283,11 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" @@ -5999,11 +6298,32 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.1.0: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" @@ -6231,6 +6551,15 @@ ini@^1.3.4: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" @@ -6251,6 +6580,15 @@ ipaddr.js@1.9.1: resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -6261,6 +6599,24 @@ is-arrayish@^0.3.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" @@ -6268,6 +6624,19 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-core-module@^2.9.0: version "2.10.0" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" @@ -6275,11 +6644,35 @@ is-core-module@^2.9.0: dependencies: has "^1.0.3" +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" @@ -6290,6 +6683,16 @@ is-generator-fn@^2.0.0: resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== + dependencies: + call-bound "^1.0.3" + get-proto "^1.0.0" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -6307,6 +6710,24 @@ is-interactive@^2.0.0: resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" @@ -6339,11 +6760,57 @@ is-promise@^4.0.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz" integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" @@ -6354,11 +6821,36 @@ is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0: resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2, is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-windows@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -6797,6 +7289,16 @@ jest-config@^29.7.0: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-diff@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.5.tgz#b40f81e0c0d13e5b81c4d62b0d0dfa6a524ee0fd" + integrity sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + pretty-format "30.0.5" + jest-diff@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz" @@ -7106,6 +7608,16 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-matcher-utils@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz#dff3334be58faea4a5e1becc228656fbbfc2467d" + integrity sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ== + dependencies: + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + jest-diff "30.0.5" + pretty-format "30.0.5" + jest-matcher-utils@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz" @@ -7146,6 +7658,21 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-message-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.5.tgz#dd12ffec91dd3fa6a59cbd538a513d8e239e070c" + integrity sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-message-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz" @@ -7206,6 +7733,15 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + jest-util "30.0.5" + jest-mock@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz" @@ -7237,6 +7773,11 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + jest-regex-util@^28.0.2: version "28.0.2" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" @@ -7683,6 +8224,18 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + jest-util@^28.0.0, jest-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" @@ -8327,7 +8880,7 @@ merge@^2.1.1: resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -8620,11 +9173,28 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.3: +object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + on-finished@^2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" @@ -8695,6 +9265,15 @@ ora@^7.0.1: string-width "^6.1.0" strip-ansi "^7.1.0" +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -8949,6 +9528,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + postcss-load-config@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz" @@ -8975,6 +9559,15 @@ prettier@^3.3.3: resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +pretty-format@30.0.5, pretty-format@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + pretty-format@^24: version "24.9.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz" @@ -9202,7 +9795,7 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -9251,6 +9844,20 @@ readline@^1.3.0: resolved "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz" integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz" @@ -9280,6 +9887,30 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" +regexp.escape@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexp.escape/-/regexp.escape-2.0.1.tgz#09e4beef9d202dbd739868f3818223f977cf91da" + integrity sha512-JItRb4rmyTzmERBkAf6J87LjDPy/RscIwmaJQ3gsFlAzrmZbZU8LwBw5IydFZXW9hqpgbPlGbMhtpqtuAhMgtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + for-each "^0.3.3" + safe-regex-test "^1.0.3" + +regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + regexpu-core@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz" @@ -9457,6 +10088,17 @@ rxjs@^7.0.0, rxjs@^7.8.1: dependencies: tslib "^2.1.0" +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" + isarray "^2.0.5" + safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" @@ -9467,6 +10109,23 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + safe-stable-stringify@^2.3.1: version "2.5.0" resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" @@ -9576,6 +10235,37 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" @@ -9759,6 +10449,13 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" @@ -9771,6 +10468,14 @@ stdin-discarder@^0.1.0: dependencies: bl "^5.0.0" +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + streamx@^2.15.0, streamx@^2.21.0: version "2.22.0" resolved "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz" @@ -9825,6 +10530,38 @@ string-width@^6.1.0: emoji-regex "^10.2.1" strip-ansi "^7.0.1" +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + +string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -10232,6 +10969,51 @@ type-is@^2.0.0, type-is@^2.0.1: media-typer "^1.1.0" mime-types "^3.0.0" +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + typed-query-selector@^2.12.0: version "2.12.0" resolved "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz" @@ -10251,6 +11033,16 @@ typescript@^5.4.3: resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" @@ -10460,11 +11252,64 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which@^1.2.10, which@^1.2.14: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" From 108675071ff560c8311162e2b7eb734b54d2dd44 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 8 Aug 2025 12:18:50 -0400 Subject: [PATCH 421/422] [compiler] Aggregate error reporting, separate eslint rules All error messages that are not invariants, todos, or config validation errors are now moved to a registry. This helps us check that error messages and linked documentation is uniform and up to date. This also lets us link error codes to linter reporting categories and break the single `react-compiler` rule up into many smaller rules. Changes to error reporting in React Compiler: - instead of writing raw error message, add a new ErrorCode to `CompilerErrorCodes` - create errors by constructing a `CompilerErrorDetailOptions` with the correct error code, or calling `CompilerErrorDetail.fromCode()` / `CompilerDiagnostic.fromCode()`. Changes to linter: - `react-compiler` lint rule is now broken up into many smaller lint rules, each with their own test cases. - linting no longer fails early when a non-fatal error is find. Instead, we now log and move on to lint for more errors! Todos: - Codebases previously using the eslint plugin may already have `react-compiler` eslint suppressions. Since this PR removes this rule, these codebases may get new eslint warnings on already-suppressed ranges. Some options I can think of: - Force everyone to add the new suppressions - Release a script to rewrite previous suppressions to the new suppressions. Codebases that had incomplete suppressions / waited to update may benefit here, as this still surfaces errors on previously-unseen suppressions - Hard code logic in our eslint plugin to avoid reporting if we see a `eslint-disable-next-line react-compiler`. This doesn't feel ideal - Align on eslint plugin rule names and error code mappings - I aggregated a few error codes here. For example, reassigning and modifying local variables after render now have the same error code + message. See changes fixtures for more examples --- .../src/CompilerError.ts | 267 ++++-- .../src/Entrypoint/Imports.ts | 4 +- .../src/Entrypoint/Options.ts | 9 +- .../src/Entrypoint/Pipeline.ts | 36 +- .../src/Entrypoint/Program.ts | 47 +- .../src/Entrypoint/Suppression.ts | 14 +- .../ValidateNoUntransformedReferences.ts | 33 +- .../src/Flood/TypeErrors.ts | 41 +- .../src/HIR/BuildHIR.ts | 194 ++-- .../src/HIR/Environment.ts | 10 +- .../src/HIR/HIR.ts | 15 +- .../src/HIR/HIRBuilder.ts | 27 +- .../src/Inference/DropManualMemoization.ts | 77 +- .../src/Inference/InferFunctionEffects.ts | 36 +- .../Inference/InferMutationAliasingEffects.ts | 66 +- .../src/Inference/InferReferenceEffects.ts | 16 +- .../src/Transform/TransformFire.ts | 27 +- .../src/Utils/CompilerErrorCodes.ts | 622 ++++++++++++ .../src/Utils/CompilerErrorSeverity.ts | 34 + .../src/Validation/ValidateHooksUsage.ts | 58 +- .../ValidateLocalsNotReassignedAfterRender.ts | 26 +- .../ValidateMemoizedEffectDependencies.ts | 9 +- .../Validation/ValidateNoCapitalizedCalls.ts | 14 +- .../ValidateNoDerivedComputationsInEffects.ts | 21 +- ...ValidateNoFreezingKnownMutableFunctions.ts | 9 +- .../ValidateNoImpureFunctionsInRender.ts | 19 +- .../Validation/ValidateNoJSXInTryStatement.ts | 9 +- .../Validation/ValidateNoRefAccessInRender.ts | 101 +- .../Validation/ValidateNoSetStateInEffects.ts | 23 +- .../Validation/ValidateNoSetStateInRender.ts | 31 +- .../ValidatePreservedManualMemoization.ts | 46 +- .../Validation/ValidateStaticComponents.ts | 11 +- .../src/Validation/ValidateUseMemo.ts | 28 +- ...global-in-component-tag-function.expect.md | 4 +- ...or.assign-global-in-jsx-children.expect.md | 4 +- ...n-global-in-jsx-spread-attribute.expect.md | 6 +- ...rror.bailout-on-flow-suppression.expect.md | 2 +- ...ut-on-suppression-of-custom-rule.expect.md | 4 +- ...ive-ref-validation-in-use-effect.expect.md | 2 +- ...ext-variable-only-chained-assign.expect.md | 4 +- ...variable-in-function-declaration.expect.md | 4 +- ...erences-variable-its-assigned-to.expect.md | 4 +- ...destructure-assignment-to-global.expect.md | 4 +- ...ucture-to-local-global-variables.expect.md | 4 +- ...lid-global-reassignment-indirect.expect.md | 4 +- .../error.invalid-hoisting-setstate.expect.md | 4 +- ...-argument-mutates-local-variable.expect.md | 2 +- ...valid-impure-functions-in-render.expect.md | 18 +- ...n-of-possible-props-phi-indirect.expect.md | 2 +- ...eassign-local-variable-in-effect.expect.md | 4 +- ...id-pass-mutable-function-as-prop.expect.md | 2 +- .../error.invalid-reassign-const.expect.md | 4 +- ...ssign-local-in-hook-return-value.expect.md | 4 +- ...eassign-local-variable-in-effect.expect.md | 4 +- ...-local-variable-in-hook-argument.expect.md | 4 +- ...n-local-variable-in-jsx-callback.expect.md | 4 +- ...eturn-mutable-function-from-hook.expect.md | 2 +- ....invalid-sketchy-code-use-forget.expect.md | 4 +- ...es-memoizes-with-captures-values.expect.md | 2 +- ...alid-unclosed-eslint-suppression.expect.md | 2 +- ...nconditional-set-state-in-render.expect.md | 4 +- ...ange-shared-inner-outer-function.expect.md | 4 +- ...rror.mutate-property-from-global.expect.md | 2 +- ...or.not-useEffect-external-mutate.expect.md | 4 +- ...r.object-capture-global-mutation.expect.md | 6 +- .../error.reassign-global-fn-arg.expect.md | 4 +- ....reassignment-to-global-indirect.expect.md | 8 +- .../error.reassignment-to-global.expect.md | 8 +- ...ror.sketchy-code-exhaustive-deps.expect.md | 2 +- ...rror.sketchy-code-rules-of-hooks.expect.md | 2 +- .../error.store-property-in-global.expect.md | 2 +- ...ences-later-variable-declaration.expect.md | 4 +- ...state-in-render-after-loop-break.expect.md | 2 +- ...l-set-state-in-render-after-loop.expect.md | 2 +- ...-state-in-render-with-loop-throw.expect.md | 2 +- ...r.unconditional-set-state-lambda.expect.md | 2 +- ...tate-nested-function-expressions.expect.md | 2 +- ...ror.update-global-should-bailout.expect.md | 4 +- ...ror.useMemo-non-literal-depslist.expect.md | 4 +- .../dynamic-gating-invalid-multiple.expect.md | 2 +- .../no-emit/retry-no-emit.expect.md | 5 +- ...setState-in-useEffect-transitive.expect.md | 2 +- .../invalid-setState-in-useEffect.expect.md | 2 +- ...valid-impure-functions-in-render.expect.md | 18 +- ...n-local-variable-in-jsx-callback.expect.md | 4 +- ...rozen-hoisted-storecontext-const.expect.md | 4 +- ...or.not-useEffect-external-mutate.expect.md | 4 +- ....reassignment-to-global-indirect.expect.md | 8 +- .../error.reassignment-to-global.expect.md | 8 +- .../new-mutability/retry-no-emit.expect.md | 5 +- ....validate-useMemo-named-function.expect.md | 2 - .../bailout-retry/error.todo-syntax.expect.md | 2 +- ...ror.untransformed-fire-reference.expect.md | 2 +- .../bailout-retry/error.use-no-memo.expect.md | 2 +- .../error.invalid-outside-effect.expect.md | 4 +- ...id-rewrite-deps-no-array-literal.expect.md | 2 +- ...rror.invalid-rewrite-deps-spread.expect.md | 2 +- .../__tests__/ImpureFunctionCallsRule-test.ts | 32 + .../__tests__/InvalidHooksRule-test.ts | 85 ++ .../__tests__/NoAmbiguousJsxRule-test.ts | 31 + .../__tests__/NoCapitalizedCallsRule-test.ts | 58 ++ .../__tests__/NoRefAccessInRender-tests.ts | 27 + .../__tests__/NoSetStateInEffects-tests.ts | 48 + .../__tests__/NoSetStateInRender-test.ts | 58 ++ .../__tests__/NoUnusedDirectivesRule-test.ts | 58 ++ .../__tests__/PluginTest-test.ts | 172 ++++ .../__tests__/ReactCompilerRule-test.ts | 287 ------ .../ReactCompilerRuleTypescript-test.ts | 24 +- .../__tests__/StaticComponentsRule-test.ts | 44 + .../__tests__/UnnecessaryEffects-tests.ts | 36 + .../__tests__/ValidateUseMemo-test.ts | 43 + .../__tests__/shared-utils.ts | 98 ++ .../eslint-plugin-react-compiler/package.json | 4 +- .../eslint-plugin-react-compiler/src/index.ts | 52 +- .../src/rules/ReactCompilerRule.ts | 626 ++++++------ .../src/shared/RunReactCompiler.ts | 323 +++++++ compiler/packages/snap/src/compiler.ts | 11 +- compiler/yarn.lock | 901 +++++++++++++++++- 118 files changed, 3792 insertions(+), 1460 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts delete mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index ee0d0f74c5..6bce5dca2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -10,48 +10,22 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {SourceLocation} from './HIR'; import {Err, Ok, Result} from './Utils/Result'; import {assertExhaustive} from './Utils/utils'; - -export enum ErrorSeverity { - /** - * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some - * misunderstanding on the user’s part. - */ - InvalidJS = 'InvalidJS', - /** - * JS syntax that is not supported and which we do not plan to support. Developers should - * rewrite to use supported forms. - */ - UnsupportedJS = 'UnsupportedJS', - /** - * Code that breaks the rules of React. - */ - InvalidReact = 'InvalidReact', - /** - * Incorrect configuration of the compiler. - */ - InvalidConfig = 'InvalidConfig', - /** - * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve - * memoization. - */ - CannotPreserveMemoization = 'CannotPreserveMemoization', - /** - * Unhandled syntax that we don't support yet. - */ - Todo = 'Todo', - /** - * An unexpected internal error in the compiler that indicates critical issues that can panic - * the compiler. - */ - Invariant = 'Invariant', -} +import {ErrorSeverity} from './Utils/CompilerErrorSeverity'; +import { + ErrorCode, + ErrorCodeDetails, + LinterCategory, +} from './Utils/CompilerErrorCodes'; +export {ErrorSeverity}; +export {ErrorCode, ErrorCodeDetails, LinterCategory}; export type CompilerDiagnosticOptions = { severity: ErrorSeverity; category: string; - description: string; + description?: string | null | undefined; details: Array; suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; }; export type CompilerDiagnosticDetail = @@ -86,13 +60,28 @@ export type CompilerSuggestion = description: string; }; -export type CompilerErrorDetailOptions = { +export type PlainCompilerErrorDetailOptions = { + errorCode?: void; reason: string; description?: string | null | undefined; - severity: ErrorSeverity; + severity: + | ErrorSeverity.Invariant + | ErrorSeverity.Todo + | ErrorSeverity.InvalidConfig; loc: SourceLocation | null; suggestions?: Array | null | undefined; }; +export type CodedCompilerErrorDetailOptions = { + errorCode: ErrorCode; + description?: string | null | undefined; + loc: SourceLocation | null; + suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; +}; + +export type CompilerErrorDetailOptions = + | PlainCompilerErrorDetailOptions + | CodedCompilerErrorDetailOptions; export type PrintErrorMessageOptions = { /** @@ -102,19 +91,72 @@ export type PrintErrorMessageOptions = { eslint: boolean; }; +export function makeCompilerDiagnostic( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + }, +): CompilerDiagnostic { + return makeCompilerDiagnostic(code, options); +} + export class CompilerDiagnostic { options: CompilerDiagnosticOptions; - constructor(options: CompilerDiagnosticOptions) { + /** + * Constructor is private to enforce that we either only create invariant diagnostics + * or use ErrorCodes + */ + private constructor(options: CompilerDiagnosticOptions) { this.options = options; } - static create( - options: Omit, - ): CompilerDiagnostic { + static create< + T extends CompilerDiagnosticOptions & {severity: ErrorSeverity.Invariant}, + >(options: Omit): CompilerDiagnostic { return new CompilerDiagnostic({...options, details: []}); } + static fromCode( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + details?: Array | null | undefined; + }, + ): CompilerDiagnostic { + const errorEntry = ErrorCodeDetails[code]; + let description = undefined; + if (errorEntry.description != null) { + description = errorEntry.description; + } + if (options?.description != null && options.description.length > 0) { + if (description != null && description.length > 0) { + description += ' '; + } else { + description = ''; + } + description += options.description; + } + + const diagnosticOptions: CompilerDiagnosticOptions = { + severity: errorEntry.severity, + category: errorEntry.reason, + description, + linterCategory: errorEntry.linterCategory, + suggestions: options?.suggestions, + details: options?.details ?? [], + }; + + return new CompilerDiagnostic(diagnosticOptions); + } + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return {options: {...this.options, linterCategory: undefined}}; + } + get category(): CompilerDiagnosticOptions['category'] { return this.options.category; } @@ -127,6 +169,9 @@ export class CompilerDiagnostic { get suggestions(): CompilerDiagnosticOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): CompilerDiagnosticOptions['linterCategory'] { + return this.options.linterCategory; + } withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic { this.options.details.push(detail); @@ -138,11 +183,10 @@ export class CompilerDiagnostic { } printErrorMessage(source: string, options: PrintErrorMessageOptions): string { - const buffer = [ - printErrorSummary(this.severity, this.category), - '\n\n', - this.description, - ]; + const buffer = [printErrorSummary(this.severity, this.category)]; + if (this.description != null) { + buffer.push(`\n\n${this.description}`); + } for (const detail of this.options.details) { switch (detail.kind) { case 'error': { @@ -202,13 +246,65 @@ export class CompilerErrorDetail { this.options = options; } - get reason(): CompilerErrorDetailOptions['reason'] { - return this.options.reason; + static fromCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + return new CompilerErrorDetail({ + ...details, + errorCode: code, + } as CodedCompilerErrorDetailOptions); } - get description(): CompilerErrorDetailOptions['description'] { + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return { + options: { + reason: this.reason, + description: this.description, + severity: this.severity, + loc: this.loc, + suggestions: this.suggestions, + }, + }; + } + + get reason(): string { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].reason; + } else { + return this.options.reason; + } + } + get description(): string | null | undefined { + if (this.options.errorCode != null) { + let description = undefined; + if (ErrorCodeDetails[this.options.errorCode].description != null) { + description = ErrorCodeDetails[this.options.errorCode].description; + } + if ( + this.options.description != null && + this.options.description.length > 0 + ) { + if (description != null && description.length > 0) { + description += '. '; + } else { + description = ''; + } + description += this.options.description; + } + return description; + } return this.options.description; } - get severity(): CompilerErrorDetailOptions['severity'] { + get severity(): ErrorSeverity { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].severity; + } return this.options.severity; } get loc(): CompilerErrorDetailOptions['loc'] { @@ -217,6 +313,12 @@ export class CompilerErrorDetail { get suggestions(): CompilerErrorDetailOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): LinterCategory | null | undefined { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].linterCategory; + } + return undefined; + } primaryLocation(): SourceLocation | null { return this.loc; @@ -266,7 +368,7 @@ export class CompilerError extends Error { static invariant( condition: unknown, - options: Omit, + options: Omit, ): asserts condition { if (!condition) { const errors = new CompilerError(); @@ -280,14 +382,14 @@ export class CompilerError extends Error { } } - static throwDiagnostic(options: CompilerDiagnosticOptions): never { + static throwDiagnostic(diagnostic: CompilerDiagnostic): never { const errors = new CompilerError(); - errors.pushDiagnostic(new CompilerDiagnostic(options)); + errors.pushDiagnostic(diagnostic); throw errors; } static throwTodo( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -296,34 +398,21 @@ export class CompilerError extends Error { throw errors; } - static throwInvalidJS( - options: Omit, + static throwFromCode( + code: ErrorCode, + options?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, ): never { const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidJS, - }), - ); - throw errors; - } - - static throwInvalidReact( - options: Omit, - ): never { - const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidReact, - }), - ); + errors.pushErrorDetail(CompilerErrorDetail.fromCode(code, options)); throw errors; } static throwInvalidConfig( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -392,13 +481,10 @@ export class CompilerError extends Error { } push(options: CompilerErrorDetailOptions): CompilerErrorDetail { - const detail = new CompilerErrorDetail({ - reason: options.reason, - description: options.description ?? null, - severity: options.severity, - suggestions: options.suggestions, - loc: typeof options.loc === 'symbol' ? null : options.loc, - }); + if (options instanceof CompilerErrorDetail) { + return this.pushErrorDetail(options); + } + const detail = new CompilerErrorDetail(options); return this.pushErrorDetail(detail); } @@ -407,6 +493,19 @@ export class CompilerError extends Error { return detail; } + pushErrorCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + const detail = CompilerErrorDetail.fromCode(code, details); + this.details.push(detail); + return detail; + } + hasErrors(): boolean { return this.details.length > 0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 24ce37cf72..ea1d28a62e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -38,10 +38,10 @@ export function validateRestrictedImports( ImportDeclaration(importDeclPath) { if (restrictedImports.has(importDeclPath.node.source.value)) { error.push({ - severity: ErrorSeverity.Todo, reason: 'Bailing out due to blocklisted import', description: `Import from module ${importDeclPath.node.source.value}`, loc: importDeclPath.node.loc ?? null, + severity: ErrorSeverity.Todo, }); } }, @@ -205,10 +205,10 @@ export class ProgramContext { } const error = new CompilerError(); error.push({ - severity: ErrorSeverity.Todo, reason: 'Encountered conflicting global in generated program', description: `Conflict from local binding ${name}`, loc: scope.getBinding(name)?.path.node.loc ?? null, + severity: ErrorSeverity.Todo, suggestions: null, }); return Err(error); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c13940ed10..1a8e1457ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerErrorDetail, - CompilerErrorDetailOptions, + PlainCompilerErrorDetailOptions, } from '../CompilerError'; import { EnvironmentConfig, @@ -107,6 +107,8 @@ export type PluginOptions = { * passes. * * Defaults to false + * + * TODO: rename this to lintOnly or something similar */ noEmit: boolean; @@ -234,7 +236,10 @@ export type CompileErrorEvent = { export type CompileDiagnosticEvent = { kind: 'CompileDiagnostic'; fnLoc: t.SourceLocation | null; - detail: Omit, 'suggestions'>; + detail: Omit< + Omit, + 'suggestions' + >; }; export type CompileSuccessEvent = { kind: 'CompileSuccess'; 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 a4f984f195..7a004fa18c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -100,7 +100,6 @@ import {outlineJSX} from '../Optimization/OutlineJsx'; import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls'; import {transformFire} from '../Transform'; import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender'; -import {CompilerError} from '..'; import {validateStaticComponents} from '../Validation/ValidateStaticComponents'; import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions'; import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects'; @@ -174,7 +173,7 @@ function runWithEnvironment( !env.config.disableMemoizationForDebugging && !env.config.enableChangeDetectionForDebugging ) { - dropManualMemoization(hir).unwrap(); + env.logOrThrowErrors(dropManualMemoization(hir)); log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } @@ -207,10 +206,10 @@ function runWithEnvironment( if (env.isInferredMemoEnabled) { if (env.config.validateHooksUsage) { - validateHooksUsage(hir).unwrap(); + env.logOrThrowErrors(validateHooksUsage(hir)); } - if (env.config.validateNoCapitalizedCalls) { - validateNoCapitalizedCalls(hir).unwrap(); + if (env.config.validateNoCapitalizedCalls != null) { + env.logOrThrowErrors(validateNoCapitalizedCalls(hir)); } } @@ -230,20 +229,17 @@ function runWithEnvironment( log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); if (!env.config.enableNewMutationAliasingModel) { - const fnEffectErrors = inferReferenceEffects(hir); + const fnEffectResult = inferReferenceEffects(hir); + if (env.isInferredMemoEnabled) { - if (fnEffectErrors.length > 0) { - CompilerError.throw(fnEffectErrors[0]); - } + env.logOrThrowErrors(fnEffectResult); } log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); } else { const mutabilityAliasingErrors = inferMutationAliasingEffects(hir); log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } + env.logOrThrowErrors(mutabilityAliasingErrors); } } @@ -272,10 +268,8 @@ function runWithEnvironment( }); log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } - validateLocalsNotReassignedAfterRender(hir); + env.logOrThrowErrors(mutabilityAliasingErrors.map(() => undefined)); + env.logOrThrowErrors(validateLocalsNotReassignedAfterRender(hir)); } } @@ -285,15 +279,15 @@ function runWithEnvironment( } if (env.config.validateRefAccessDuringRender) { - validateNoRefAccessInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoRefAccessInRender(hir)); } if (env.config.validateNoSetStateInRender) { - validateNoSetStateInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoSetStateInRender(hir)); } if (env.config.validateNoDerivedComputationsInEffects) { - validateNoDerivedComputationsInEffects(hir); + env.logOrThrowErrors(validateNoDerivedComputationsInEffects(hir)); } if (env.config.validateNoSetStateInEffects) { @@ -305,14 +299,14 @@ function runWithEnvironment( } if (env.config.validateNoImpureFunctionsInRender) { - validateNoImpureFunctionsInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoImpureFunctionsInRender(hir)); } if ( env.config.validateNoFreezingKnownMutableFunctions || env.config.enableNewMutationAliasingModel ) { - validateNoFreezingKnownMutableFunctions(hir).unwrap(); + env.logOrThrowErrors(validateNoFreezingKnownMutableFunctions(hir)); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 79bbee37a5..825f83a2a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -7,11 +7,7 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, ErrorSeverity} from '../CompilerError'; import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; @@ -32,6 +28,7 @@ import { } from './Suppression'; import {GeneratedSource} from '../HIR'; import {Err, Ok, Result} from '../Utils/Result'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; export type CompilerPass = { opts: PluginOptions; @@ -101,12 +98,9 @@ function findDirectivesDynamicGating( if (t.isValidIdentifier(maybeMatch[1])) { result.push({directive, match: maybeMatch[1]}); } else { - errors.push({ - reason: `Dynamic gating directive is not a valid JavaScript identifier`, + errors.pushErrorCode(ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, { description: `Found '${directive.value.value}'`, - severity: ErrorSeverity.InvalidReact, loc: directive.loc ?? null, - suggestions: null, }); } } @@ -115,14 +109,11 @@ function findDirectivesDynamicGating( return Err(errors); } else if (result.length > 1) { const error = new CompilerError(); - error.push({ - reason: `Multiple dynamic gating directives found`, + error.pushErrorCode(ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, { description: `Expected a single directive but found [${result .map(r => r.directive.value.value) .join(', ')}]`, - severity: ErrorSeverity.InvalidReact, loc: result[0].directive.loc ?? null, - suggestions: null, }); return Err(error); } else if (result.length === 1) { @@ -451,14 +442,12 @@ export function compileProgram( if (programContext.hasModuleScopeOptOut) { if (compiledFns.length > 0) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: - 'Unexpected compiled functions when module scope opt-out is present', - severity: ErrorSeverity.Invariant, - loc: null, - }), - ); + error.push({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }); handleError(error, programContext, null); } return null; @@ -591,9 +580,7 @@ function processFn( let compiledFn: CodegenFunction; const compileResult = tryCompileFunction(fn, fnType, programContext); if (compileResult.kind === 'error') { - if (directives.optOut != null) { - logError(compileResult.error, programContext, fn.node.loc ?? null); - } else { + if (directives.optOut == null) { handleError(compileResult.error, programContext, fn.node.loc ?? null); } const retryResult = retryCompileFunction(fn, fnType, programContext); @@ -692,7 +679,7 @@ function tryCompileFunction( fn, programContext.opts.environment, fnType, - 'all_features', + programContext.opts.noEmit ? 'lint_only' : 'all_features', programContext, programContext.opts.logger, programContext.filename, @@ -805,15 +792,7 @@ function shouldSkipCompilation( if (pass.opts.sources) { if (pass.filename === null) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: `Expected a filename but found none.`, - description: - "When the 'sources' config options is specified, the React compiler will only compile files with a name", - severity: ErrorSeverity.InvalidConfig, - loc: null, - }), - ); + error.pushErrorCode(ErrorCode.FILENAME_NOT_SET); handleError(error, pass, null); return true; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts index ee341b111f..75828aa108 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, - ErrorSeverity, + ErrorCode, } from '../CompilerError'; import {assertExhaustive} from '../Utils/utils'; import {GeneratedSource} from '../HIR'; @@ -165,14 +165,12 @@ export function suppressionsToCompilerError( let reason, suggestion; switch (suppressionRange.source) { case 'Eslint': - reason = - 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled'; + reason = ErrorCode.BAILOUT_ESLINT_SUPPRESSION; suggestion = 'Remove the ESLint suppression and address the React error'; break; case 'Flow': - reason = - 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow'; + reason = ErrorCode.BAILOUT_FLOW_SUPPRESSION; suggestion = 'Remove the Flow suppression and address the React error'; break; default: @@ -182,10 +180,8 @@ export function suppressionsToCompilerError( ); } error.pushDiagnostic( - CompilerDiagnostic.create({ - category: reason, - description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``, - severity: ErrorSeverity.InvalidReact, + CompilerDiagnostic.fromCode(reason, { + description: `Found suppression \`${suppressionRange.disableComment.value.trim()}\``, suggestions: [ { description: suggestion, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 16d7c3713c..5271d66886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -8,27 +8,24 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..'; +import {CompilerError, EnvironmentConfig, Logger} from '..'; import {getOrInsertWith} from '../Utils/utils'; import {Environment, GeneratedSource} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; import {CompileProgramMetadata} from './Program'; -import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError'; +import {CompilerDiagnostic} from '../CompilerError'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; -function throwInvalidReact( - options: Omit, +function logAndThrowDiagnostic( + diagnostic: CompilerDiagnostic, {logger, filename}: TraversalState, ): never { - const detail: CompilerDiagnosticOptions = { - severity: ErrorSeverity.InvalidReact, - ...options, - }; logger?.logEvent(filename, { kind: 'CompileError', fnLoc: null, - detail: new CompilerDiagnostic(detail), + detail: diagnostic, }); - CompilerError.throwDiagnostic(detail); + CompilerError.throwDiagnostic(diagnostic); } function isAutodepsSigil( @@ -90,10 +87,8 @@ function assertValidEffectImportReference( * as it may have already been transformed by the compiler (and not * memoized). */ - throwInvalidReact( - { - category: - 'Cannot infer dependencies of this effect. This will break your build!', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.DID_NOT_INFER_DEPS, { description: 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' + (maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''), @@ -104,7 +99,7 @@ function assertValidEffectImportReference( loc: parent.node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } @@ -121,10 +116,8 @@ function assertValidFireImportReference( paths[0], context.transformErrors, ); - throwInvalidReact( - { - category: - '[Fire] Untransformed reference to compiler-required feature.', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.CANNOT_COMPILE_FIRE, { description: 'Either remove this `fire` call or ensure it is successfully transformed by the compiler' + maybeErrorDiagnostic @@ -137,7 +130,7 @@ function assertValidFireImportReference( loc: paths[0].node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts b/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts index fa3f551ff5..85d6bd0457 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts @@ -1,4 +1,5 @@ import {CompilerError, SourceLocation} from '..'; +import {ErrorCode} from '../CompilerError'; import { ConcreteType, printConcrete, @@ -12,8 +13,8 @@ export function unsupportedLanguageFeature( desc: string, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Typedchecker does not currently support language feature: ${desc}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Typedchecker does not currently support language feature: ${desc}`, loc, }); } @@ -49,16 +50,16 @@ export function raiseUnificationErrors( loc, }); } else if (errs.length === 1) { - CompilerError.throwInvalidJS({ - reason: `Unable to unify types because ${printUnificationError(errs[0])}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to unify types because ${printUnificationError(errs[0])}`, loc, }); } else { const messages = errs .map(err => `\t* ${printUnificationError(err)}`) .join('\n'); - CompilerError.throwInvalidJS({ - reason: `Unable to unify types because:\n${messages}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to unify types because:\n${messages}`, loc, }); } @@ -69,21 +70,21 @@ export function unresolvableTypeVariable( id: VariableId, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Unable to resolve free variable ${id} to a concrete type`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to resolve free variable ${id} to a concrete type`, loc, }); } export function cannotAddVoid(explicit: boolean, loc: SourceLocation): never { if (explicit) { - CompilerError.throwInvalidJS({ - reason: `Undefined is not a valid operand of \`+\``, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Undefined is not a valid operand of \`+\``, loc, }); } else { - CompilerError.throwInvalidJS({ - reason: `Value may be undefined, which is not a valid operand of \`+\``, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Value may be undefined, which is not a valid operand of \`+\``, loc, }); } @@ -93,8 +94,8 @@ export function unsupportedTypeAnnotation( desc: string, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Typedchecker does not currently support type annotation: ${desc}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Typedchecker does not currently support type annotation: ${desc}`, loc, }); } @@ -106,16 +107,16 @@ export function checkTypeArgumentArity( loc: SourceLocation, ): void { if (expected !== actual) { - CompilerError.throwInvalidJS({ - reason: `Expected ${desc} to have ${expected} type parameters, got ${actual}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Expected ${desc} to have ${expected} type parameters, got ${actual}`, loc, }); } } export function notAFunction(desc: string, loc: SourceLocation): void { - CompilerError.throwInvalidJS({ - reason: `Cannot call ${desc} because it is not a function`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Cannot call ${desc} because it is not a function`, loc, }); } @@ -124,8 +125,8 @@ export function notAPolymorphicFunction( desc: string, loc: SourceLocation, ): void { - CompilerError.throwInvalidJS({ - reason: `Cannot call ${desc} with type arguments because it is not a polymorphic function`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Cannot call ${desc} with type arguments because it is not a polymorphic function`, loc, }); } 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 3b11670146..26f05f58ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -12,6 +12,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, + ErrorCode, ErrorSeverity, } from '../CompilerError'; import {Err, Ok, Result} from '../Utils/Result'; @@ -170,14 +171,12 @@ export function lower( ); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.Todo, - category: `Handle ${param.node.type} parameters`, + CompilerDiagnostic.fromCode(ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, { description: `[BuildHIR] Add support for ${param.node.type} parameters.`, }).withDetail({ kind: 'error', loc: param.node.loc ?? null, - message: 'Unsupported parameter type', + message: `Unsupported parameter type: ${param.node.type}`, }), ); } @@ -201,14 +200,12 @@ export function lower( directives = body.get('directives').map(d => d.node.value.value); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidJS, - category: `Unexpected function body kind`, + CompilerDiagnostic.fromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`, }).withDetail({ kind: 'error', loc: body.node.loc ?? null, - message: 'Expected a block statement or expression', + message: 'Change this to a block statement or expression', }), ); } @@ -769,12 +766,12 @@ function lowerStatement( const testExpr = case_.get('test'); if (testExpr.node == null) { if (hasDefault) { - builder.errors.push({ - reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, - severity: ErrorSeverity.InvalidJS, - loc: case_.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + { + loc: case_.node.loc ?? null, + }, + ); break; } hasDefault = true; @@ -886,19 +883,20 @@ function lowerStatement( if (builder.isContextIdentifier(id)) { if (kind === InstructionKind.Const) { const declRangeStart = declaration.parentPath.node.start!; - builder.errors.push({ - reason: `Expect \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: id.node.loc ?? null, - suggestions: [ - { - description: 'Change to a `let` declaration', - op: CompilerSuggestionOperation.Replace, - range: [declRangeStart, declRangeStart + 5], // "const".length - text: 'let', - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: id.node.loc ?? null, + suggestions: [ + { + description: 'Change to a `let` declaration', + op: CompilerSuggestionOperation.Replace, + range: [declRangeStart, declRangeStart + 5], // "const".length + text: 'let', + }, + ], + }, + ); } lowerValueToTemporary(builder, { kind: 'DeclareContext', @@ -932,13 +930,13 @@ function lowerStatement( } } } else { - builder.errors.push({ - reason: `Expected variable declaration to be an identifier if no initializer was provided`, - description: `Got a \`${id.type}\``, - severity: ErrorSeverity.InvalidJS, - loc: stmt.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + { + description: `Got a \`${id.type}\``, + loc: stmt.node.loc ?? null, + }, + ); } } return; @@ -1374,12 +1372,8 @@ function lowerStatement( return; } case 'WithStatement': { - builder.errors.push({ - reason: `JavaScript 'with' syntax is not supported`, - description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_WITH, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1394,12 +1388,8 @@ function lowerStatement( * and complex enough to support that we don't anticipate supporting anytime soon. Developers * are encouraged to lift classes out of component/hook declarations. */ - builder.errors.push({ - reason: 'Inline `class` declarations are not supported', - description: `Move class declarations outside of components/hooks`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_INNER_CLASS, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1423,12 +1413,8 @@ function lowerStatement( case 'ImportDeclaration': case 'TSExportAssignment': case 'TSImportEqualsDeclaration': { - builder.errors.push({ - reason: - 'JavaScript `import` and `export` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_IMPORT_EXPORT, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1438,12 +1424,8 @@ function lowerStatement( return; } case 'TSNamespaceExportDeclaration': { - builder.errors.push({ - reason: - 'TypeScript `namespace` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_TS_NAMESPACE, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1698,12 +1680,8 @@ function lowerExpression( const expr = exprPath as NodePath; const calleePath = expr.get('callee'); if (!calleePath.isExpression()) { - builder.errors.push({ - reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, - description: `Got a \`${calleePath.node.type}\``, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_NEW_EXPRESSION, { loc: calleePath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -1800,12 +1778,12 @@ function lowerExpression( last = lowerExpressionToTemporary(builder, item); } if (last === null) { - builder.errors.push({ - reason: `Expected sequence expression to have at least one expression`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + { + loc: expr.node.loc ?? null, + }, + ); } else { lowerValueToTemporary(builder, { kind: 'StoreLocal', @@ -2289,18 +2267,18 @@ function lowerExpression( }); for (const [name, locations] of Object.entries(fbtLocations)) { if (locations.length > 1) { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support duplicate fbt tags', - description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, - details: locations.map(loc => { - return { - kind: 'error', - message: `Multiple \`<${tagName}:${name}>\` tags found`, - loc, - }; + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_DUPLICATE_FBT_TAGS, { + description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, + details: locations.map(loc => { + return { + kind: 'error', + message: `Multiple \`<${tagName}:${name}>\` tags found`, + loc, + }; + }), }), - }); + ); } } } @@ -2389,11 +2367,8 @@ function lowerExpression( const quasis = expr.get('quasis'); if (subexprs.length !== quasis.length - 1) { - builder.errors.push({ - reason: `Unexpected quasi and subexpression lengths in template literal`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_QUASI_LENGTHS, { loc: exprPath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -2441,24 +2416,23 @@ function lowerExpression( }; } } else { - builder.errors.push({ - reason: `Only object properties can be deleted`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: [ - { - description: 'Remove this line', - range: [expr.node.start!, expr.node.end!], - op: CompilerSuggestionOperation.Remove, - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + { + loc: expr.node.loc ?? null, + suggestions: [ + { + description: 'Remove this line', + range: [expr.node.start!, expr.node.end!], + op: CompilerSuggestionOperation.Remove, + }, + ], + }, + ); return {kind: 'UnsupportedNode', node: expr.node, loc: exprLoc}; } } else if (expr.node.operator === 'throw') { - builder.errors.push({ - reason: `Throw expressions are not supported`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_THROW_EXPRESSION, { loc: expr.node.loc ?? null, suggestions: [ { @@ -3285,10 +3259,8 @@ function lowerJsxElementName( const name = exprPath.node.name.name; const tag = `${namespace}:${name}`; if (namespace.indexOf(':') !== -1 || name.indexOf(':') !== -1) { - builder.errors.push({ - reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + builder.errors.pushErrorCode(ErrorCode.INVALID_JSX_NAMESPACED_NAME, { description: `Got \`${namespace}\` : \`${name}\``, - severity: ErrorSeverity.InvalidJS, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3583,11 +3555,7 @@ function lowerIdentifier( } default: { if (binding.kind === 'Global' && binding.name === 'eval') { - builder.errors.push({ - reason: `The 'eval' function is not supported`, - description: - 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_EVAL, { loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3653,9 +3621,7 @@ function lowerIdentifierForAssignment( binding.bindingKind === 'const' && kind === InstructionKind.Reassign ) { - builder.errors.push({ - reason: `Cannot reassign a \`const\` variable`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, { loc: path.node.loc ?? null, description: binding.identifier.name != null @@ -3710,12 +3676,13 @@ function lowerAssignment( let temporary; if (builder.isContextIdentifier(lvalue)) { if (kind === InstructionKind.Const && !isHoistedIdentifier) { - builder.errors.push({ - reason: `Expected \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: lvalue.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: lvalue.node.loc ?? null, + suggestions: null, + }, + ); } if ( @@ -3726,7 +3693,8 @@ function lowerAssignment( ) { builder.errors.push({ reason: `Unexpected context variable kind`, - severity: ErrorSeverity.InvalidJS, + description: `Expected one of Const, Reassign, Let, Function, got ${kind}`, + severity: ErrorSeverity.Invariant, loc: lvalue.node.loc ?? null, suggestions: null, }); 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 957c5ab84a..ad1815a62d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -93,7 +93,7 @@ export const MacroSchema = z.union([ z.tuple([z.string(), z.array(MacroMethodSchema)]), ]); -export type CompilerMode = 'all_features' | 'no_inferred_memo'; +export type CompilerMode = 'all_features' | 'no_inferred_memo' | 'lint_only'; export type Macro = z.infer; export type MacroMethod = z.infer; @@ -829,6 +829,14 @@ export class Environment { } } + logOrThrowErrors(errors: Result): void { + if (this.compilerMode === 'lint_only') { + this.logErrors(errors); + } else { + errors.unwrap(); + } + } + isContextIdentifier(node: t.Identifier): boolean { return this.#contextIdentifiers.has(node); } 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 51715b3c1e..89b2c0fc41 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -15,6 +15,7 @@ import {Type, makeType} from './Types'; import {z} from 'zod'; import type {AliasingEffect} from '../Inference/AliasingEffects'; import {isReservedWord} from '../Utils/Keyword'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -1322,18 +1323,18 @@ export function forkTemporaryIdentifier( */ export function makeIdentifierName(name: string): ValidatedIdentifier { if (isReservedWord(name)) { - CompilerError.throwInvalidJS({ - reason: 'Expected a non-reserved identifier name', - loc: GeneratedSource, - description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, - suggestions: null, - }); + CompilerError.throwFromCode( + ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + { + loc: GeneratedSource, + description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, + }, + ); } else { CompilerError.invariant(t.isValidIdentifier(name), { reason: `Expected a valid identifier name`, loc: GeneratedSource, description: `\`${name}\` is not a valid JavaScript identifier`, - suggestions: null, }); } return { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 81959ea361..1d71452d0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -7,7 +7,7 @@ import {Binding, NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {Environment} from './Environment'; import { BasicBlock, @@ -37,6 +37,7 @@ import { mapTerminalSuccessors, terminalFallthrough, } from './visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -308,19 +309,17 @@ export default class HIRBuilder { resolveBinding(node: t.Identifier): Identifier { if (node.name === 'fbt') { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support local variables named `fbt`', - description: - 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', - details: [ - { - kind: 'error', - message: 'Rename to avoid conflict with fbt plugin', - loc: node.loc ?? GeneratedSource, - }, - ], - }); + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, { + details: [ + { + kind: 'error', + message: 'Rename to avoid conflict with fbt plugin', + loc: node.loc ?? GeneratedSource, + }, + ], + }), + ); } const originalName = node.name; let name = originalName; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 7aeb3edb22..fa9bddd3f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -5,12 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, - SourceLocation, -} from '..'; +import {CompilerDiagnostic, CompilerError, SourceLocation} from '..'; import { CallExpression, Effect, @@ -35,6 +30,7 @@ import { makeInstructionId, } from '../HIR'; import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; type ManualMemoCallee = { @@ -299,12 +295,11 @@ function extractManualMemoizationArgs( >; if (fnPlace == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected a callback function to be passed to ${kind}`, - description: `Expected a callback function to be passed to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + kind === 'useMemo' + ? ErrorCode.INVALID_USE_MEMO_NO_ARG0 + : ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Expected a callback function to be passed to ${kind}`, @@ -314,12 +309,11 @@ function extractManualMemoizationArgs( } if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Unexpected spread argument to ${kind}`, - description: `Unexpected spread argument to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + fnPlace.kind === 'Spread' + ? ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT + : ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Unexpected spread argument to ${kind}`, @@ -334,12 +328,9 @@ function extractManualMemoizationArgs( ); if (maybeDepsList == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list for ${kind} to be an array literal`, - description: `Expected the dependency list for ${kind} to be an array literal`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + ).withDetail({ kind: 'error', loc: depsListPlace.loc, message: `Expected the dependency list for ${kind} to be an array literal`, @@ -352,12 +343,9 @@ function extractManualMemoizationArgs( const maybeDep = sidemap.maybeDeps.get(dep.identifier.id); if (maybeDep == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + ).withDetail({ kind: 'error', loc: dep.loc, message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, @@ -457,16 +445,16 @@ export function dropManualMemoization( if (funcToCheck !== undefined && funcToCheck.loweredFunc.func) { if (!hasNonVoidReturn(funcToCheck.loweredFunc.func)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks must return a value', - description: `This ${ - manualMemo.loadInstr.value.kind === 'PropertyLoad' - ? 'React.useMemo' - : 'useMemo' - } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + { + description: `This ${ + manualMemo.loadInstr.value.kind === 'PropertyLoad' + ? 'React.useMemo' + : 'useMemo' + } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, + }, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: 'useMemo() callbacks must return a value', @@ -497,12 +485,9 @@ export function dropManualMemoization( */ if (!sidemap.functions.has(fnPlace.identifier.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the first argument to be an inline function expression`, - description: `Expected the first argument to be an inline function expression`, - suggestions: [], - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + ).withDetail({ kind: 'error', loc: fnPlace.loc, message: `Expected the first argument to be an inline function expression`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts index a01ca188a0..5fcf8a3cae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts @@ -25,6 +25,7 @@ import { isRefOrRefValue, } from '../HIR'; import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {assertExhaustive} from '../Utils/utils'; interface State { @@ -62,22 +63,20 @@ function inferOperandEffect(state: State, place: Place): null | FunctionEffect { // We ignore mutations of primitives since this is not a React-specific problem value.kind !== ValueKind.Primitive ) { - let reason = getWriteErrorReason(value); + let errorCode = getWriteErrorReason(value); return { kind: value.reason.size === 1 && value.reason.has(ValueReason.Global) ? 'GlobalMutation' : 'ReactMutation', error: { - reason, + errorCode, description: place.identifier.name !== null && place.identifier.name.kind === 'named' ? `Found mutation of \`${place.identifier.name.value}\`` : null, loc: place.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }; } @@ -266,11 +265,8 @@ export function inferInstructionFunctionEffects( functionEffects.push({ kind: 'GlobalMutation', error: { - reason: - 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + errorCode: ErrorCode.INVALID_WRITE_GLOBAL, loc: instr.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }); break; @@ -324,28 +320,28 @@ function isEffectSafeOutsideRender(effect: FunctionEffect): boolean { return effect.kind === 'GlobalMutation'; } -export function getWriteErrorReason(abstractValue: AbstractValue): string { +export function getWriteErrorReason(abstractValue: AbstractValue): ErrorCode { if (abstractValue.reason.has(ValueReason.Global)) { - return 'Modifying a variable defined outside a component or hook is not allowed. Consider using an effect'; + return ErrorCode.INVALID_WRITE_GLOBAL; } else if (abstractValue.reason.has(ValueReason.JsxCaptured)) { - return 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX'; + return ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX; } else if (abstractValue.reason.has(ValueReason.Context)) { - return `Modifying a value returned from 'useContext()' is not allowed.`; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT; } else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) { - return 'Modifying a value returned from a function whose return value should not be mutated'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE; } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) { - return 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS; } else if (abstractValue.reason.has(ValueReason.State)) { - return "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead"; + return ErrorCode.INVALID_WRITE_STATE; } else if (abstractValue.reason.has(ValueReason.ReducerState)) { - return "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead"; + return ErrorCode.INVALID_WRITE_REDUCER_STATE; } else if (abstractValue.reason.has(ValueReason.Effect)) { - return 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()'; + return ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY; } else if (abstractValue.reason.has(ValueReason.HookCaptured)) { - return 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook'; + return ErrorCode.INVALID_WRITE_HOOK_CAPTURED; } else if (abstractValue.reason.has(ValueReason.HookReturn)) { - return 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed'; + return ErrorCode.INVALID_WRITE_HOOK_RETURN; } else { - return 'This modifies a variable that React considers immutable'; + return ErrorCode.INVALID_WRITE_GENERIC; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index 2adf78fe05..60146d8335 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -9,7 +9,6 @@ import { CompilerDiagnostic, CompilerError, Effect, - ErrorSeverity, SourceLocation, ValueKind, } from '..'; @@ -69,6 +68,7 @@ import {getWriteErrorReason} from './InferFunctionEffects'; import prettyFormat from 'pretty-format'; import {createTemporaryPlace} from '../HIR/HIRBuilder'; import {AliasingEffect, AliasingSignature, hashEffect} from './AliasingEffects'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; const DEBUG = false; @@ -442,7 +442,7 @@ function applySignature( const value = state.kind(effect.value); switch (value.kind) { case ValueKind.Frozen: { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -455,10 +455,9 @@ function applySignature( effects.push({ kind: 'MutateFrozen', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -1026,22 +1025,20 @@ function applyEffect( const hoistedAccess = context.hoistedContextDeclarations.get( effect.value.identifier.declarationId, ); - const diagnostic = CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access variable before it is declared', - description: `${variable ?? 'This variable'} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.`, - }); + const diagnostic = CompilerDiagnostic.fromCode( + ErrorCode.INVALID_ACCESS_BEFORE_INIT, + ); if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) { diagnostic.withDetail({ kind: 'error', loc: hoistedAccess.loc, - message: `${variable ?? 'variable'} accessed before it is declared`, + message: `${variable ?? 'This variable'} is accessed before it is declared`, }); } diagnostic.withDetail({ kind: 'error', loc: effect.value.loc, - message: `${variable ?? 'variable'} is declared here`, + message: `${variable ?? 'This variable'} is declared here`, }); applyEffect( @@ -1056,7 +1053,7 @@ function applyEffect( effects, ); } else { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -1075,10 +1072,9 @@ function applyEffect( ? 'MutateFrozen' : 'MutateGlobal', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -2006,15 +2002,12 @@ function computeSignatureForInstruction( effects.push({ kind: 'MutateGlobal', place: value.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'Cannot reassign variables declared outside of the component/hook', - description: `Variable ${variable} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)`, - }).withDetail({ + error: CompilerDiagnostic.fromCode( + ErrorCode.INVALID_WRITE_GLOBAL, + ).withDetail({ kind: 'error', loc: instr.loc, - message: `${variable} cannot be reassigned`, + message: `${variable} should not be reassigned`, }), }); effects.push({kind: 'Assign', from: value.value, into: lvalue}); @@ -2105,19 +2098,16 @@ function computeEffectsForLegacySignature( effects.push({ kind: 'Impure', place: receiver, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - }).withDetail({ - kind: 'error', - loc, - message: 'Cannot call impure function', - }), + error: CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail( + { + kind: 'error', + loc, + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', + }, + ), }); } const stores: Array = []; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 1b0856791a..a806ebd4bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError'; +import {CompilerError} from '../CompilerError'; import {Environment} from '../HIR'; import { AbstractValue, @@ -48,6 +48,7 @@ import { eachTerminalOperand, eachTerminalSuccessor, } from '../HIR/visitors'; +import {Err, Ok, Result} from '../Utils/Result'; import {assertExhaustive, Set_isSuperset} from '../Utils/utils'; import { inferTerminalFunctionEffects, @@ -106,7 +107,7 @@ const UndefinedValue: InstructionValue = { export default function inferReferenceEffects( fn: HIRFunction, options: {isFunctionExpression: boolean} = {isFunctionExpression: false}, -): Array { +): Result { /* * Initial state contains function params * TODO: include module declarations here as well @@ -247,10 +248,17 @@ export default function inferReferenceEffects( if (options.isFunctionExpression) { fn.effects = functionEffects; - return []; } else { - return transformFunctionEffectErrors(functionEffects); + const errors = transformFunctionEffectErrors(functionEffects); + if (errors.length > 0) { + const compilerError = new CompilerError(); + for (const detail of errors) { + compilerError.push(detail); + } + return Err(compilerError); + } } + return Ok(void 0); } type FreezeAction = {values: Set; reason: Set}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts index f88c85f2f0..dbb84944b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts @@ -42,6 +42,7 @@ import { import {eachInstructionOperand} from '../HIR/visitors'; import {printSourceLocationLine} from '../HIR/PrintHIR'; import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * TODO(jmbrown): @@ -50,8 +51,6 @@ import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; * - React.useEffect calls */ -const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`'; - export function transformFire(fn: HIRFunction): void { const context = new Context(fn.env); replaceFireFunctions(fn, context); @@ -178,9 +177,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } else if (value.args.length > 1 && value.args[1].kind === 'Spread') { @@ -188,9 +185,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].place.loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -243,11 +238,9 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } else { context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description: '`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed', - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else { @@ -262,10 +255,8 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else if (value.kind === 'CallExpression') { @@ -394,9 +385,7 @@ function ensureNoRemainingCalleeCaptures( description: `All uses of ${calleeName} must be either used with a fire() call in \ this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \ ${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -411,9 +400,7 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { context.pushError({ loc: place.identifier.loc, description: 'Cannot use `fire` outside of a useEffect function', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts new file mode 100644 index 0000000000..51eff972ca --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts @@ -0,0 +1,622 @@ +import {ErrorSeverity} from './CompilerErrorSeverity'; + +export enum LinterCategory { + RULES_OF_HOOKS = 'rules-of-hooks', + IMPURE_FUNCTIONS = 'impure-functions', + JSX_IN_TRY = 'jsx-in-try', + NO_REF_ACCESS_IN_RENDER = 'no-ref-access-in-render', + VALIDATE_MANUAL_MEMO = 'validate-manual-memo', + DYNAMIC_MANUAL_MEMO = 'dynamic-manual-memo', + + EXHAUSTIVE_DEPS = 'exhaustive-deps', + MEMOIZED_DEPENDENCIES = 'memoized-dependencies', // not real! + INVALID_WRITE = 'invalid-write', + CAPITALIZED_CALLS = 'capitalized-calls', + STATIC_COMPONENTS = 'static-components', + NO_SET_STATE_IN_RENDER = 'no-set-state-in-render', + NO_SET_STATE_IN_EFFECTS = 'no-set-state-in-effects', + UNNECESSARY_EFFECTS = 'unnecessary-effects', + + UNSUPPORTED_SYNTAX = 'unsupported-syntax', + + TODO_SYNTAX = 'todo-syntax', + + COMPILER_CONFIG = 'compiler-config', +} + +export enum ErrorCode { + HOOK_CALL_STATIC, + HOOK_INVALID_REFERENCE, + HOOK_CALL_REACTIVE, + HOOK_CALL_NOT_TOP_LEVEL, + IMPURE_FUNCTIONS, + JSX_IN_TRY, + NO_REF_ACCESS_IN_RENDER, + WRITE_AFTER_RENDER, + REASSIGN_AFTER_RENDER, + REASSIGN_IN_ASYNC, + INVALID_WRITE, + CAPITALIZED_CALLS, + STATIC_COMPONENTS, + INVALID_USE_MEMO_CALLBACK_RETURN, + INVALID_USE_MEMO_CALLBACK_PARAMETERS, + INVALID_USE_MEMO_CALLBACK_ASYNC, + INVALID_USE_MEMO_NO_ARG0, + INVALID_USE_CALLBACK_NO_ARG0, + DYNAMIC_MANUAL_MEMO_CALLBACK, + DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + + INVALID_SET_STATE_IN_RENDER, + INVALID_SET_STATE_IN_MEMO, + INVALID_SET_STATE_IN_EFFECTS, + + NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + + DYNAMIC_GATING_IS_NOT_IDENTIFIER, + DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + FILENAME_NOT_SET, + + INVALID_WRITE_GLOBAL, + INVALID_WRITE_FROZEN_VALUE_JSX, + INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + INVALID_WRITE_IMMUTABLE_ARGS, + INVALID_WRITE_STATE, + INVALID_WRITE_REDUCER_STATE, + INVALID_WRITE_EFFECT_DEPENDENCY, + INVALID_WRITE_HOOK_CAPTURED, + INVALID_WRITE_HOOK_RETURN, + INVALID_ACCESS_BEFORE_INIT, + INVALID_WRITE_GENERIC, + + MEMOIZED_EFFECT_DEPENDENCIES, + + INVALID_SYNTAX_MULTIPLE_DEFAULTS, + INVALID_SYNTAX_REASSIGNED_CONST, + INVALID_SYNTAX_BAD_VARIABLE_DECL, + UNSUPPORTED_WITH, + UNSUPPORTED_INNER_CLASS, + INVALID_IMPORT_EXPORT, + INVALID_TS_NAMESPACE, + UNSUPPORTED_NEW_EXPRESSION, + UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + INVALID_QUASI_LENGTHS, + INVALID_SYNTAX_DELETE_EXPRESSION, + UNSUPPORTED_THROW_EXPRESSION, + INVALID_JSX_NAMESPACED_NAME, + UNSUPPORTED_EVAL, + INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + INVALID_JAVASCRIPT_AST, + BAILOUT_ESLINT_SUPPRESSION, + BAILOUT_FLOW_SUPPRESSION, + MANUAL_MEMO_MUTATED_LATER, + MANUAL_MEMO_REMOVED, + MANUAL_MEMO_DEPENDENCIES_CONFLICT, + + CANNOT_COMPILE_FIRE, + DID_NOT_INFER_DEPS, + + /** Todo syntax */ + TODO_CONFLICTING_FBT_IDENTIFIER, + TODO_DUPLICATE_FBT_TAGS, + UNKNOWN_FUNCTION_PARAMETERS, +} + +type ErrorCodeType = { + code: ErrorCode; + description?: string; + severity: ErrorSeverity; + reason: string; + linterCategory: LinterCategory | null; +}; + +export const ErrorCodeDetails: Record = { + [ErrorCode.HOOK_CALL_STATIC]: { + code: ErrorCode.HOOK_CALL_STATIC, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_INVALID_REFERENCE]: { + code: ErrorCode.HOOK_INVALID_REFERENCE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_REACTIVE]: { + code: ErrorCode.HOOK_CALL_REACTIVE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_NOT_TOP_LEVEL]: { + code: ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.IMPURE_FUNCTIONS]: { + code: ErrorCode.IMPURE_FUNCTIONS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot call impure functions during render', + description: + 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).', + linterCategory: LinterCategory.IMPURE_FUNCTIONS, + }, + [ErrorCode.JSX_IN_TRY]: { + code: ErrorCode.JSX_IN_TRY, + severity: ErrorSeverity.InvalidReact, + reason: 'Avoid constructing JSX within try/catch', + description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, + linterCategory: LinterCategory.JSX_IN_TRY, + }, + [ErrorCode.NO_REF_ACCESS_IN_RENDER]: { + code: ErrorCode.NO_REF_ACCESS_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access refs during render', + description: + 'React refs are values that are not needed for rendering. Refs should only be accessed ' + + 'outside of render, such as in event handlers or effects. ' + + 'Accessing a ref value (the `current` property) during render can cause your component ' + + 'not to update as expected (https://react.dev/reference/react/useRef)', + linterCategory: LinterCategory.NO_REF_ACCESS_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_EFFECTS]: { + code: ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Calling setState synchronously within an effect can trigger cascading renders', + description: + 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + + 'In general, the body of an effect should do one or both of the following:\n' + + '* Update external systems with the latest state from React.\n' + + '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + + 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + + '(https://react.dev/learn/you-might-not-need-an-effect)', + linterCategory: LinterCategory.NO_SET_STATE_IN_EFFECTS, + }, + [ErrorCode.WRITE_AFTER_RENDER]: { + code: ErrorCode.WRITE_AFTER_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot modify local variables after render completes', + description: `This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.REASSIGN_AFTER_RENDER]: { + code: ErrorCode.REASSIGN_AFTER_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign local variables after render completes', + description: `Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.REASSIGN_IN_ASYNC]: { + code: ErrorCode.REASSIGN_IN_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variable in async function', + description: + 'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE]: { + code: ErrorCode.INVALID_WRITE, + severity: ErrorSeverity.InvalidReact, + reason: 'This value cannot be modified', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.CAPITALIZED_CALLS]: { + code: ErrorCode.CAPITALIZED_CALLS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config', + linterCategory: LinterCategory.CAPITALIZED_CALLS, + }, + [ErrorCode.STATIC_COMPONENTS]: { + code: ErrorCode.STATIC_COMPONENTS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot create components during render', + linterCategory: LinterCategory.STATIC_COMPONENTS, + }, + [ErrorCode.INVALID_USE_MEMO_NO_ARG0]: { + code: ErrorCode.INVALID_USE_MEMO_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useMemo`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_CALLBACK_NO_ARG0]: { + code: ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useCallback`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useMemo', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + + [ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useCallback', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not accept parameters', + description: + 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not be async or generator functions', + description: + 'useMemo() callbacks are called once and must synchronously return a value.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks must return a value', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the first argument to be an inline function expression`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list of useMemo or useCallback to be an array literal`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY]: { + code: ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_SET_STATE_IN_RENDER]: { + code: ErrorCode.INVALID_SET_STATE_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState during render may trigger an infinite loop', + description: + 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_MEMO]: { + code: ErrorCode.INVALID_SET_STATE_IN_MEMO, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState from useMemo may trigger an infinite loop', + description: + 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS]: { + code: ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', + linterCategory: LinterCategory.UNNECESSARY_EFFECTS, + }, + + /** Invalid writes */ + [ErrorCode.INVALID_WRITE_GLOBAL]: { + code: ErrorCode.INVALID_WRITE_GLOBAL, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variables declared outside of the component/hook', + description: + 'Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX]: { + code: ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + severity: ErrorSeverity.InvalidReact, + reason: `Modifying a value returned from 'useContext()' is not allowed.`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a function whose return value should not be mutated', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_STATE]: { + code: ErrorCode.INVALID_WRITE_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_REDUCER_STATE]: { + code: ErrorCode.INVALID_WRITE_REDUCER_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY]: { + code: ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_CAPTURED]: { + code: ErrorCode.INVALID_WRITE_HOOK_CAPTURED, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_RETURN]: { + code: ErrorCode.INVALID_WRITE_HOOK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_ACCESS_BEFORE_INIT]: { + code: ErrorCode.INVALID_ACCESS_BEFORE_INIT, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access variable before it is declared', + description: `Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_GENERIC]: { + code: ErrorCode.INVALID_WRITE_GENERIC, + severity: ErrorSeverity.InvalidReact, + reason: 'This modifies a variable that React considers immutable', + linterCategory: LinterCategory.INVALID_WRITE, + }, + + /** Compiler Config */ + [ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER]: { + code: ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, + severity: ErrorSeverity.InvalidReact, + reason: 'Dynamic gating directive is not a valid JavaScript identifier', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES]: { + code: ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + severity: ErrorSeverity.InvalidReact, + reason: 'Expected a single dynamic gating directive', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.FILENAME_NOT_SET]: { + code: ErrorCode.FILENAME_NOT_SET, + severity: ErrorSeverity.InvalidConfig, + reason: `Expected a filename but found none.`, + description: + "When the 'sources' config options is specified, the React compiler will only compile files with a name", + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + + /** Effect dependencies */ + [ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES]: { + code: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, + reason: + 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.MEMOIZED_DEPENDENCIES, + }, + + /** Invalid / unsupported syntax */ + [ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS]: { + code: ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST]: { + code: ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + reason: `Expect \`const\` declaration not to be reassigned`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL]: { + code: ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + reason: `Expected variable declaration to be an identifier if no initializer was provided`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_WITH]: { + code: ErrorCode.UNSUPPORTED_WITH, + reason: `JavaScript 'with' syntax is not supported`, + description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_INNER_CLASS]: { + code: ErrorCode.UNSUPPORTED_INNER_CLASS, + reason: 'Inline `class` declarations are not supported', + description: `Move class declarations outside of components/hooks`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_IMPORT_EXPORT]: { + code: ErrorCode.INVALID_IMPORT_EXPORT, + reason: + 'JavaScript `import` and `export` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_TS_NAMESPACE]: { + code: ErrorCode.INVALID_TS_NAMESPACE, + reason: + 'TypeScript `namespace` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_NEW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_NEW_EXPRESSION, + reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + reason: `Expected sequence expression to have at least one expression`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_QUASI_LENGTHS]: { + code: ErrorCode.INVALID_QUASI_LENGTHS, + reason: `Unexpected quasi and subexpression lengths in template literal`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION]: { + code: ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + reason: `Only object properties can be deleted`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_THROW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_THROW_EXPRESSION, + reason: `Throw expressions are not supported`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JSX_NAMESPACED_NAME]: { + code: ErrorCode.INVALID_JSX_NAMESPACED_NAME, + reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EVAL]: { + code: ErrorCode.UNSUPPORTED_EVAL, + reason: `The 'eval' function is not supported`, + description: + 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME]: { + code: ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + reason: 'Expected a non-reserved identifier name', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JAVASCRIPT_AST]: { + code: ErrorCode.INVALID_JAVASCRIPT_AST, + reason: 'Encountered invalid JavaScript', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.BAILOUT_ESLINT_SUPPRESSION]: { + code: ErrorCode.BAILOUT_ESLINT_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_REMOVED]: { + code: ErrorCode.MANUAL_MEMO_REMOVED, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + + /** + * This is left vague as fire is very experimental + */ + [ErrorCode.CANNOT_COMPILE_FIRE]: { + code: ErrorCode.CANNOT_COMPILE_FIRE, + reason: 'Cannot compile `fire`', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.DID_NOT_INFER_DEPS]: { + code: ErrorCode.DID_NOT_INFER_DEPS, + reason: + 'Cannot infer dependencies of this effect. This will break your build!', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + [ErrorCode.BAILOUT_FLOW_SUPPRESSION]: { + code: ErrorCode.BAILOUT_FLOW_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + /** Syntax React Compiler may eventually support */ + [ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER]: { + code: ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, + reason: 'Support local variables named `fbt`', + description: + 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.TODO_DUPLICATE_FBT_TAGS]: { + code: ErrorCode.TODO_DUPLICATE_FBT_TAGS, + reason: 'Support duplicate fbt tags', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.UNKNOWN_FUNCTION_PARAMETERS]: { + code: ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, + severity: ErrorSeverity.Todo, + reason: 'Currently unsupported function parameter syntax', + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_MUTATED_LATER]: { + code: ErrorCode.MANUAL_MEMO_MUTATED_LATER, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: [ + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', + 'This dependency may be mutated later, which could cause the value to change unexpectedly.', + ].join(''), + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT]: { + code: ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + '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.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts new file mode 100644 index 0000000000..f399cadb9d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts @@ -0,0 +1,34 @@ +export enum ErrorSeverity { + /** + * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some + * misunderstanding on the user’s part. + */ + InvalidJS = 'InvalidJS', + /** + * JS syntax that is not supported and which we do not plan to support. Developers should + * rewrite to use supported forms. + */ + UnsupportedJS = 'UnsupportedJS', + /** + * Code that breaks the rules of React. + */ + InvalidReact = 'InvalidReact', + /** + * Incorrect configuration of the compiler. + */ + InvalidConfig = 'InvalidConfig', + /** + * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve + * memoization. + */ + CannotPreserveMemoization = 'CannotPreserveMemoization', + /** + * Unhandled syntax that we don't support yet. + */ + Todo = 'Todo', + /** + * An unexpected internal error in the compiler that indicates critical issues that can panic + * the compiler. + */ + Invariant = 'Invariant', +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts index b28228339c..d04e163960 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts @@ -6,11 +6,7 @@ */ import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, CompilerErrorDetail} from '../CompilerError'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {isHookName} from '../HIR/Environment'; import { @@ -27,6 +23,7 @@ import { } from '../HIR/visitors'; import {assertExhaustive} from '../Utils/utils'; import {Result} from '../Utils/Result'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; /** * Represents the possible kinds of value which may be stored at a given Place during @@ -111,8 +108,6 @@ export function validateHooksUsage( // Once a particular hook has a conditional call error, don't report any further issues for this hook setKind(place, Kind.Error); - const reason = - 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)'; const previousError = typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined; @@ -120,15 +115,15 @@ export function validateHooksUsage( * In some circumstances such as optional calls, we may first encounter a "hook may not be referenced as normal values" error. * If that same place is also used as a conditional call, upgrade the error to a conditonal hook error */ - if (previousError === undefined || previousError.reason !== reason) { + if ( + previousError === undefined || + previousError.reason !== + ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason + ) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason, + CompilerErrorDetail.fromCode(ErrorCode.HOOK_CALL_STATIC, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -139,13 +134,8 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + CompilerErrorDetail.fromCode(ErrorCode.HOOK_INVALID_REFERENCE, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -156,14 +146,12 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', - loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }), + CompilerErrorDetail.fromCode( + ErrorCodeDetails[ErrorCode.HOOK_CALL_REACTIVE].code, + { + loc: place.loc, + }, + ), ); } } @@ -424,7 +412,7 @@ export function validateHooksUsage( } for (const [, error] of errorsByPlace) { - errors.push(error); + errors.pushErrorDetail(error); } return errors.asResult(); } @@ -446,16 +434,10 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void { : instr.value.property; const hookKind = getHookKind(fn.env, callee.identifier); if (hookKind != null) { - errors.pushErrorDetail( - new CompilerErrorDetail({ - severity: ErrorSeverity.InvalidReact, - reason: - 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', - loc: callee.loc, - description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, - suggestions: null, - }), - ); + errors.pushErrorCode(ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, { + loc: callee.loc, + description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, + }); } break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts index 31bbf8c94d..d7c6bacea8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; import {HIRFunction, IdentifierId, Place} from '../HIR'; import { eachInstructionLValue, @@ -13,13 +13,17 @@ import { eachTerminalOperand, } from '../HIR/visitors'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Ok, Result} from '../Utils/Result'; /** * Validates that local variables cannot be reassigned after render. * This prevents a category of bugs in which a closure captures a * binding from one render but does not update */ -export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { +export function validateLocalsNotReassignedAfterRender( + fn: HIRFunction, +): Result { const contextVariables = new Set(); const reassignment = getContextReassignment( fn, @@ -35,18 +39,15 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable after render completes', - description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.REASSIGN_AFTER_RENDER).withDetail({ kind: 'error', loc: reassignment.loc, message: `Cannot reassign ${variable} after render completes`, }), ); - throw errors; + return errors.asResult(); } + return Ok(undefined); } function getContextReassignment( @@ -90,12 +91,9 @@ function getContextReassignment( ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable in async function', - description: - 'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead', - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.REASSIGN_IN_ASYNC, + ).withDetail({ kind: 'error', loc: reassignment.loc, message: `Cannot reassign ${variable}`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts index b33cfb1512..90c714c557 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity} from '..'; +import {CompilerError} from '..'; import { Identifier, Instruction, @@ -22,6 +22,7 @@ import { ReactiveFunctionVisitor, visitReactiveFunction, } from '../ReactiveScopes/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -108,12 +109,8 @@ class Visitor extends ReactiveFunctionVisitor { isUnmemoized(deps.identifier, this.scopes)) ) { state.push({ - reason: - 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', - description: null, - severity: ErrorSeverity.CannotPreserveMemoization, + errorCode: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null, - suggestions: null, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts index 8989cb1ac2..72e237f660 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..'; +import {CompilerError, EnvironmentConfig} from '..'; import {HIRFunction, IdentifierId} from '../HIR'; import {DEFAULT_GLOBALS} from '../HIR/Globals'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateNoCapitalizedCalls( @@ -33,8 +34,6 @@ export function validateNoCapitalizedCalls( const errors = new CompilerError(); const capitalLoadGlobals = new Map(); const capitalizedProperties = new Map(); - const reason = - 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config'; for (const [, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { switch (value.kind) { @@ -55,11 +54,9 @@ export function validateNoCapitalizedCalls( const calleeIdentifier = value.callee.identifier.id; const calleeName = capitalLoadGlobals.get(calleeIdentifier); if (calleeName != null) { - CompilerError.throwInvalidReact({ - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${calleeName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; @@ -78,12 +75,9 @@ export function validateNoCapitalizedCalls( const propertyIdentifier = value.property.identifier.id; const propertyName = capitalizedProperties.get(propertyIdentifier); if (propertyName != null) { - errors.push({ - severity: ErrorSeverity.InvalidReact, - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${propertyName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts index d026a94ed4..5ec74f0dae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity, SourceLocation} from '..'; +import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, BlockId, @@ -19,6 +19,8 @@ import { eachInstructionValueOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Result} from '../Utils/Result'; /** * Validates that useEffect is not used for derived computations which could/should @@ -43,7 +45,9 @@ import { * const fullName = firstName + ' ' + lastName; * ``` */ -export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { +export function validateNoDerivedComputationsInEffects( + fn: HIRFunction, +): Result { const candidateDependencies: Map = new Map(); const functions: Map = new Map(); const locals: Map = new Map(); @@ -96,9 +100,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { } } } - if (errors.hasErrors()) { - throw errors; - } + return errors.asResult(); } function validateEffect( @@ -218,13 +220,6 @@ function validateEffect( } for (const loc of setStateLocations) { - errors.push({ - reason: - 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', - description: null, - severity: ErrorSeverity.InvalidReact, - loc, - suggestions: null, - }); + errors.pushErrorCode(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, {loc}); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts index 7a79c74780..ebf73f1e05 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts @@ -5,7 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; +import {ErrorCode} from '../CompilerError'; import { FunctionEffect, HIRFunction, @@ -65,11 +66,7 @@ export function validateNoFreezingKnownMutableFunctions( ? `\`${place.identifier.name.value}\`` : 'a local variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot modify local variables after render completes', - description: `This argument is a function which may reassign or mutate ${variable} after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`, - }) + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER) .withDetail({ kind: 'error', loc: operand.loc, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts index 85adb79ceb..ee452fc08a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {HIRFunction} from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -35,19 +36,13 @@ export function validateNoImpureFunctionsInRender( ); if (signature != null && signature.impure === true) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail({ kind: 'error', loc: callee.loc, - message: 'Cannot call impure function', + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts index eea6c0a08e..bab00d7159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts @@ -5,8 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {BlockId, HIRFunction} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -35,11 +36,7 @@ export function validateNoJSXInTryStatement( case 'JsxExpression': case 'JsxFragment': { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Avoid constructing JSX within try/catch', - description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.JSX_IN_TRY).withDetail({ kind: 'error', loc: value.loc, message: 'Avoid constructing JSX within try/catch', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts index e1c17625f4..1ffc5d013c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { BlockId, HIRFunction, @@ -26,6 +22,7 @@ import { eachPatternOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Err, Ok, Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -467,11 +464,9 @@ function validateNoRefAccessInRenderImpl( if (fnType.fn.readRefEffect) { didError = true; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.NO_REF_ACCESS_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, message: `This function accesses a ref value`, @@ -730,15 +725,13 @@ function destructure( function guardCheck(errors: CompilerError, operand: Place, env: Env): void { if (env.get(operand.identifier.id)?.kind === 'Guard') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -754,15 +747,13 @@ function validateNoRefValueAccess( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -780,15 +771,13 @@ function validateNoRefPassedToFunction( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Passing a ref to a function may read its value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Passing a ref to a function may read its value during render`, + }, + ), ); } } @@ -802,15 +791,13 @@ function validateNoRefUpdate( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'Ref' || type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Cannot update ref during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Cannot update ref during render`, + }, + ), ); } } @@ -823,21 +810,13 @@ function validateNoDirectRefValueAccess( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: type.loc ?? operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: type.loc ?? operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } - -const ERROR_DESCRIPTION = - 'React refs are values that are not needed for rendering. Refs should only be accessed ' + - 'outside of render, such as in event handlers or effects. ' + - 'Accessing a ref value (the `current` property) during render can cause your component ' + - 'not to update as expected (https://react.dev/reference/react/useRef)'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts index 9c4efa380c..0a5638277d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { HIRFunction, IdentifierId, @@ -20,6 +16,7 @@ import { Place, } from '../HIR'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -95,19 +92,9 @@ export function validateNoSetStateInEffects( const setState = setStateFunctions.get(arg.identifier.id); if (setState !== undefined) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState synchronously within an effect can trigger cascading renders', - description: - 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + - 'In general, the body of an effect should do one or both of the following:\n' + - '* Update external systems with the latest state from React.\n' + - '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + - 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + - '(https://react.dev/learn/you-might-not-need-an-effect)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + ).withDetail({ kind: 'error', loc: setState.loc, message: diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts index 81209d61c6..dc67540ee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts @@ -5,14 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, isSetStateType} from '../HIR'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -127,14 +124,9 @@ function validateNoSetStateInRenderImpl( ) { if (activeManualMemoId !== null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState from useMemo may trigger an infinite loop', - description: - 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_MEMO, + ).withDetail({ kind: 'error', loc: callee.loc, message: 'Found setState() within useMemo()', @@ -142,17 +134,12 @@ function validateNoSetStateInRenderImpl( ); } else if (unconditionalBlocks.has(block.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState during render may trigger an infinite loop', - description: - 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, - message: 'Found setState() within useMemo()', + message: 'Found setState() call here', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index 6bb59247da..7776e15cbb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError, ErrorCode} from '../CompilerError'; import { DeclarationId, Effect, @@ -280,13 +276,8 @@ function validateInferredDep( } } errorState.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - '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. ', + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, { + description: DEBUG || // If the dependency is a named variable then we can report it. Otherwise only print in debug mode (dep.identifier.name != null && dep.identifier.name.kind === 'named') @@ -300,9 +291,6 @@ function validateInferredDep( : 'Inferred dependency not present in source' }.` : '', - ] - .join('') - .trim(), suggestions: null, }).withDetail({ kind: 'error', @@ -534,15 +522,9 @@ class Visitor extends ReactiveFunctionVisitor { !this.prunedScopes.has(identifier.scope.id) ) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', - 'This dependency may be mutated later, which could cause the value to change unexpectedly.', - ].join(''), - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.MANUAL_MEMO_MUTATED_LATER, + ).withDetail({ kind: 'error', loc, message: 'This dependency may be modified later', @@ -582,18 +564,10 @@ class Visitor extends ReactiveFunctionVisitor { for (const identifier of decls) { if (isUnmemoized(identifier, this.scopes)) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. ', - DEBUG - ? `${printIdentifier(identifier)} was not memoized.` - : '', - ] - .join('') - .trim(), + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_REMOVED, { + description: DEBUG + ? `${printIdentifier(identifier)} was not memoized.` + : '', }).withDetail({ kind: 'error', loc, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts index 7f5fb408b4..12722fa2d5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, SourceLocation} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -64,9 +61,7 @@ export function validateStaticComponents( ); if (location != null) { error.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot create components during render', + CompilerDiagnostic.fromCode(ErrorCode.STATIC_COMPONENTS, { description: `Components created during render will reset their state each time they are created. Declare components outside of render. `, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts index 69ab401c89..05531ff211 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateUseMemo(fn: HIRFunction): Result { @@ -73,13 +70,9 @@ export function validateUseMemo(fn: HIRFunction): Result { ? firstParam.loc : firstParam.place.loc; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks may not accept parameters', - description: - 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + ).withDetail({ kind: 'error', loc, message: 'Callbacks with parameters are not supported', @@ -89,14 +82,9 @@ export function validateUseMemo(fn: HIRFunction): Result { if (body.loweredFunc.func.async || body.loweredFunc.func.generator) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'useMemo() callbacks may not be async or generator functions', - description: - 'useMemo() callbacks are called once and must synchronously return a value.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + ).withDetail({ kind: 'error', loc: body.loc, message: 'Async and generator functions are not supported', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md index ce42e65125..e47107723f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-component-tag-function.ts:3:4 1 | function Component() { 2 | const Foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | return ; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md index ee57ea6eb0..5acfcde730 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md @@ -22,13 +22,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-jsx-children.ts:3:4 1 | function Component() { 2 | const foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | // Children are generally access/called during render, so 6 | // modifying a global in a children function is almost diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md index 8476885de7..eb2516e386 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md @@ -18,13 +18,15 @@ function Component() { ``` Found 1 error: -Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.assign-global-in-jsx-spread-attribute.ts:4:4 2 | function Component() { 3 | const foo = () => { > 4 | someGlobal = true; - | ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + | ^^^^^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | return
; 7 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md index 6e522e1666..a5530a1180 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md @@ -20,7 +20,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `$FlowFixMe[react-rule-hook]` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `$FlowFixMe[react-rule-hook]` error.bailout-on-flow-suppression.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index 3221f97731..b348bc6191 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -23,7 +23,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable my-app/react-rule` error.bailout-on-suppression-of-custom-rule.ts:3:0 1 | // @eslintSuppressionRules:["my-app","react-rule"] @@ -36,7 +36,7 @@ error.bailout-on-suppression-of-custom-rule.ts:3:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line my-app/react-rule` error.bailout-on-suppression-of-custom-rule.ts:7:2 5 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-old-inference-false-positive-ref-validation-in-use-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-old-inference-false-positive-ref-validation-in-use-effect.expect.md index cc0ad9de11..27e61795c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-old-inference-false-positive-ref-validation-in-use-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-old-inference-false-positive-ref-validation-in-use-effect.expect.md @@ -40,7 +40,7 @@ Found 1 error: Error: Cannot modify local variables after render completes -This argument is a function which may reassign or mutate a local variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead. +This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead. error.bug-old-inference-false-positive-ref-validation-in-use-effect.ts:20:12 18 | ); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md index 6e9887c5ac..6869b2798f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md @@ -30,9 +30,9 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.context-variable-only-chained-assign.ts:10:19 8 | }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md index e5c28e6e36..d9d8e7d988 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md @@ -19,9 +19,9 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.declare-reassign-variable-in-function-declaration.ts:4:4 2 | let x = null; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md index a8a83f6b11..04a2614dff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md @@ -17,9 +17,9 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `callback` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.function-expression-references-variable-its-assigned-to.ts:3:4 1 | function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md index 4b49c5f653..96485fb584 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md @@ -17,12 +17,12 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `x` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-assignment-to-global.ts:2:3 1 | function useFoo(props) { > 2 | [x] = props; - | ^ `x` cannot be reassigned + | ^ `x` should not be reassigned 3 | return {x}; 4 | } 5 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md index 6da3b558bd..bae0df94af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-to-local-global-variables.ts:3:6 1 | function Component(props) { 2 | let a; > 3 | [a, b] = props.value; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 4 | 5 | return [a, b]; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md index 8e8b7917d7..f3f2cff6e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md @@ -39,13 +39,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-global-reassignment-indirect.ts:9:4 7 | 8 | const setGlobal = () => { > 9 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 10 | }; 11 | const indirectSetGlobal = () => { 12 | setGlobal(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md index 291d3873b4..8c512ae350 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md @@ -42,13 +42,13 @@ Found 1 error: Error: Cannot access variable before it is declared -`setState` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized error.invalid-hoisting-setstate.ts:19:18 17 | * $2 = Function context=setState 18 | */ > 19 | useEffect(() => setState(2), []); - | ^^^^^^^^ `setState` accessed before it is declared + | ^^^^^^^^ `setState` is accessed before it is declared 20 | 21 | const [state, setState] = useState(0); 22 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hook-function-argument-mutates-local-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hook-function-argument-mutates-local-variable.expect.md index 49709441df..e6daf353f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hook-function-argument-mutates-local-variable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hook-function-argument-mutates-local-variable.expect.md @@ -21,7 +21,7 @@ Found 1 error: Error: Cannot modify local variables after render completes -This argument is a function which may reassign or mutate `cache` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead. +This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead. error.invalid-hook-function-argument-mutates-local-variable.ts:5:10 3 | function useFoo() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md index 3155d64329..cf6b33ce8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md index 4ac7af5bae..768e0300bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md @@ -23,7 +23,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.invalid-mutation-of-possible-props-phi-indirect.ts:4:4 2 | let x = cond ? someGlobal : props.foo; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md index 437fbcb38c..d656650fd6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md @@ -48,9 +48,9 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.invalid-nested-function-reassign-local-variable-in-effect.ts:7:6 5 | // Create the reassignment function inside another function, then return it diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-mutable-function-as-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-mutable-function-as-prop.expect.md index 3c70bfe886..17872f264f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-mutable-function-as-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-mutable-function-as-prop.expect.md @@ -21,7 +21,7 @@ Found 1 error: Error: Cannot modify local variables after render completes -This argument is a function which may reassign or mutate `cache` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead. +This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead. error.invalid-pass-mutable-function-as-prop.ts:7:18 5 | cache.set('key', 'value'); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md index 25b9a5c186..72bfa49422 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md @@ -15,7 +15,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign a `const` variable +Error: Expect `const` declaration not to be reassigned `x` is declared as const. @@ -23,7 +23,7 @@ error.invalid-reassign-const.ts:3:2 1 | function Component() { 2 | const x = 0; > 3 | x = 1; - | ^ Cannot reassign a `const` variable + | ^ Expect `const` declaration not to be reassigned 4 | } 5 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md index 6379515a05..9847189e40 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md @@ -17,9 +17,9 @@ function useFoo() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.invalid-reassign-local-in-hook-return-value.ts:4:4 2 | let x = 0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md index 368b312022..d602179005 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md @@ -49,9 +49,9 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.invalid-reassign-local-variable-in-effect.ts:7:4 5 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md index 8c7973377d..c2c8c95071 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md @@ -50,9 +50,9 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.invalid-reassign-local-variable-in-hook-argument.ts:8:4 6 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index 3ecbcc97c3..08c75786f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -43,9 +43,9 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.invalid-reassign-local-variable-in-jsx-callback.ts:5:4 3 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-return-mutable-function-from-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-return-mutable-function-from-hook.expect.md index 2648398e50..cc3ec573c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-return-mutable-function-from-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-return-mutable-function-from-hook.expect.md @@ -23,7 +23,7 @@ Found 1 error: Error: Cannot modify local variables after render completes -This argument is a function which may reassign or mutate `cache` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead. +This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead. error.invalid-return-mutable-function-from-hook.ts:7:9 5 | useHook(); // for inference to kick in diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md index 96be8584be..14d6068b97 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ @@ -32,7 +32,7 @@ error.invalid-sketchy-code-use-forget.ts:1:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:5:2 3 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md index 8fe4602c89..fc5c39aa8d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md @@ -51,7 +51,7 @@ Found 1 error: Error: Cannot modify local variables after render completes -This argument is a function which may reassign or mutate `cache` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead. +This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead. 19 | map: TInput => TOutput 20 | ): TInput => TOutput { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md index e19cee7532..d77327b90d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md @@ -40,7 +40,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-unclosed-eslint-suppression.ts:2:0 1 | // Note: Everything below this is sketchy diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md index fa9e20a418..f10764dc70 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md @@ -29,7 +29,7 @@ error.invalid-unconditional-set-state-in-render.ts:6:2 4 | const aliased = setX; 5 | > 6 | setX(1); - | ^^^^ Found setState() within useMemo() + | ^^^^ Found setState() call here 7 | aliased(2); 8 | 9 | return x; @@ -42,7 +42,7 @@ error.invalid-unconditional-set-state-in-render.ts:7:2 5 | 6 | setX(1); > 7 | aliased(2); - | ^^^^^^^ Found setState() within useMemo() + | ^^^^^^^ Found setState() call here 8 | 9 | return x; 10 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md index 337b9dd30c..2190f7cb85 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md @@ -34,9 +34,9 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `a` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.mutable-range-shared-inner-outer-function.ts:8:6 6 | const f = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md index 78530caf4a..9903cf2a1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.mutate-property-from-global.ts:4:9 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md index c7b1ac5f45..508aea8d27 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:5:4 3 | function Component(props) { @@ -34,7 +34,7 @@ error.not-useEffect-external-mutate.ts:5:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md index 89bcedf956..606c1c42c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md @@ -24,13 +24,15 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Modifying a variable defined outside a component or hook is not allowed. Consider using an effect +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.object-capture-global-mutation.ts:4:4 2 | function Foo() { 3 | const x = () => { > 4 | window.href = 'foo'; - | ^^^^^^ Modifying a variable defined outside a component or hook is not allowed. Consider using an effect + | ^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | const y = {x}; 7 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md index 2c409ea5b5..cd7f202a1c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md @@ -28,13 +28,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassign-global-fn-arg.ts:5:4 3 | export default function MyApp() { 4 | const fn = () => { > 5 | b = 2; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 6 | }; 7 | return foo(fn); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md index 8835f19ad1..6963940109 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md @@ -21,26 +21,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:4:4 2 | const foo = () => { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | }; 7 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | }; 7 | foo(); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md index 4d259dd8d4..88ae3a7ae8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md @@ -18,26 +18,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:3:2 1 | function Component() { 2 | // Cannot assign to globals > 3 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 4 | moduleLocal = true; 5 | } 6 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | // Cannot assign to globals 3 | someUnknownGlobal = true; > 4 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 5 | } 6 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md index 9c87cafff1..0bcfae2e9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md @@ -24,7 +24,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` error.sketchy-code-exhaustive-deps.ts:6:7 4 | () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md index 7077b733b0..bd1b3ed4c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md @@ -25,7 +25,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` error.sketchy-code-rules-of-hooks.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md index 7ffe3f84cf..544532b3b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.store-property-in-global.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md index a88d43b352..cb4e042a40 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md @@ -19,9 +19,9 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `onClick` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.todo-function-expression-references-later-variable-declaration.ts:3:4 1 | function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md index 3d54bcd75c..6f9da8fda9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-after-loop-break.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md index c892066bf8..6ab4ecb36e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md @@ -27,7 +27,7 @@ error.unconditional-set-state-in-render-after-loop.ts:6:2 4 | for (const _ of props) { 5 | } > 6 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 7 | return state; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md index a617a2f572..69c81c3ff8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-with-loop-throw.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md index dfb5c7b56f..79369f3608 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md @@ -30,7 +30,7 @@ error.unconditional-set-state-lambda.ts:8:2 6 | setX(1); 7 | }; > 8 | foo(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 9 | 10 | return [x]; 11 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md index f03b514c3f..c8a775499a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md @@ -38,7 +38,7 @@ error.unconditional-set-state-nested-function-expressions.ts:16:2 14 | bar(); 15 | }; > 16 | baz(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 17 | 18 | return [x]; 19 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md index 8432be198b..cfd27c7356 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md @@ -23,13 +23,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `renderCount` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.update-global-should-bailout.ts:3:2 1 | let renderCount = 0; 2 | function useFoo() { > 3 | renderCount += 1; - | ^^^^^^^^^^^^^^^^ `renderCount` cannot be reassigned + | ^^^^^^^^^^^^^^^^ `renderCount` should not be reassigned 4 | return renderCount; 5 | } 6 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md index 188814ee02..1531425c20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md @@ -30,9 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Expected the dependency list for useMemo to be an array literal - -Expected the dependency list for useMemo to be an array literal +Error: Expected the dependency list of useMemo or useCallback to be an array literal error.useMemo-non-literal-depslist.ts:10:4 8 | return text.toUpperCase(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md index d8250c6f57..b4f642df43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Expected a single dynamic gating directive","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md index 08eb396bb1..8d565a8caa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":333},"end":{"line":11,"column":43,"index":357},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":248},"end":{"line":8,"column":46,"index":292},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":277},"end":{"line":8,"column":34,"index":280},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":316},"end":{"line":11,"column":54,"index":368},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":349},"end":{"line":11,"column":42,"index":356},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index b629bfef27..0925a9b982 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -65,7 +65,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index c16890e52e..3e3135929b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -45,7 +45,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md index a9782a3b9e..a6f268555c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index babb4e8969..dafc6ced7d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -44,9 +44,9 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot reassign local variables after render completes -Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. +Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. error.invalid-reassign-local-variable-in-jsx-callback.ts:6:4 4 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md index d78e4becec..bef031723c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md @@ -35,12 +35,12 @@ Found 1 error: Error: Cannot access variable before it is declared -`data` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized 9 | // TDZ violation! 10 | const onRefetch = useCallback(() => { > 11 | refetch(data); - | ^^^^ `data` accessed before it is declared + | ^^^^ `data` is accessed before it is declared 12 | }, [refetch]); 13 | 14 | // The context variable gets frozen here since it's passed to a hook diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md index 80a12e5d40..f1cc5c4808 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md @@ -22,7 +22,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | function Component(props) { @@ -35,7 +35,7 @@ error.not-useEffect-external-mutate.ts:6:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:7:4 5 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md index 41ed513912..bb4d91a4bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md @@ -22,26 +22,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | const foo = () => { 4 | // Cannot assign to globals > 5 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 6 | moduleLocal = true; 7 | }; 8 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:6:4 4 | // Cannot assign to globals 5 | someUnknownGlobal = true; > 6 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 7 | }; 8 | foo(); 9 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md index 6089255fd5..84339b0759 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md @@ -19,26 +19,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | function Component() { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | } 7 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:5:2 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | } 7 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md index 2e0c890367..83f28bc06d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":365},"end":{"line":11,"column":43,"index":389},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":280},"end":{"line":8,"column":46,"index":324},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":309},"end":{"line":8,"column":34,"index":312},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":348},"end":{"line":11,"column":54,"index":400},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":381},"end":{"line":11,"column":42,"index":388},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md index 27af59e175..f433f8cb88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md @@ -24,8 +24,6 @@ Found 1 error: Error: Expected the first argument to be an inline function expression -Expected the first argument to be an inline function expression - error.validate-useMemo-named-function.ts:9:20 7 | // for now. 8 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index 006d2a49c0..bda9c3b694 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -31,7 +31,7 @@ function Component({prop1}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:4) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index 8481ed2c57..9604f69476 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -15,7 +15,7 @@ console.log(fire == null); ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index f84686bc36..02753ce345 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -32,7 +32,7 @@ function Component({props, bar}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md index 81c36a362c..cdb0726f2e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md @@ -26,7 +26,7 @@ function Component({props, bar}) { ``` Found 2 errors: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. @@ -39,7 +39,7 @@ error.invalid-outside-effect.ts:8:2 10 | useCallback(() => { 11 | fire(foo(props)); -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md index 96cea9c08f..d82f1ee1b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md @@ -27,7 +27,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md index 4dc5336ebe..a841813630 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md @@ -30,7 +30,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts new file mode 100644 index 0000000000..9f665a0477 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts @@ -0,0 +1,32 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/CompilerError'; +import {NoImpureFunctionCallsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no impure function calls rule', NoImpureFunctionCallsRule, { + valid: [], + invalid: [ + { + name: 'Known impure function calls are caught', + code: normalizeIndent` + function Component() { + const date = Date.now(); + const now = performance.now(); + const rand = Math.random(); + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts new file mode 100644 index 0000000000..9192087b31 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts @@ -0,0 +1,85 @@ +/** + * 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 {RulesOfHooksRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('rules-of-hooks', RulesOfHooksRule, { + valid: [ + { + name: 'Basic example', + code: normalizeIndent` + function Component() { + useHook(); + return
Hello world
; + } + `, + }, + { + name: 'Violation with Flow suppression', + code: ` + // Valid since error already suppressed with flow. + function useHook() { + if (cond) { + // $FlowFixMe[react-rule-hook] + useConditionalHook(); + } + } + `, + }, + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function useConditional() { + cond ?? useConditionalHook(); + props.cond && useConditionalHook(); + return
Hello world
; + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts new file mode 100644 index 0000000000..5f2aa63340 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts @@ -0,0 +1,31 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/CompilerError'; +import {NoAmbiguousJsxRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ambiguous JSX rule', NoAmbiguousJsxRule, { + valid: [], + invalid: [ + { + name: 'JSX in try blocks are warned against', + code: normalizeIndent` + function Component(props) { + let el; + try { + el = ; + } catch { + return null; + } + return el; + } + `, + errors: [makeTestCaseError(ErrorCode.JSX_IN_TRY)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts new file mode 100644 index 0000000000..821cab8007 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {NoCapitalizedCallsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidCapitalizedCallsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('no-capitalized-calls', NoCapitalizedCallsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + import Child from './Child'; + function Component() { + return <> + {Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Method call violation', + code: normalizeIndent` + import myModule from './MyModule'; + function Component() { + return <> + {myModule.Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + import Child1 from './Child1'; + import MyModule from './MyModule'; + function Component() { + return <> + {Child1()} + {MyModule.Child2()} + ; + }`, + errors: [ + invalidCapitalizedCallsRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts new file mode 100644 index 0000000000..91717ddd7b --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts @@ -0,0 +1,27 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/CompilerError'; +import {NoRefAccessInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ref access in render rule', NoRefAccessInRenderRule, { + valid: [], + invalid: [ + { + name: 'validate against simple ref access in render', + code: normalizeIndent` + function Component(props) { + const ref = useRef(null); + const value = ref.current; + return value; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_REF_ACCESS_IN_RENDER)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts new file mode 100644 index 0000000000..ee022519ba --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts @@ -0,0 +1,48 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/CompilerError'; +import {NoSetStateInEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in effects rule', NoSetStateInEffectsRule, { + valid: [], + invalid: [ + { + name: 'unconditional setState in useEffect', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + setState(s => s + 1); + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + { + name: 'conditional setState in render', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + if (state % 2 === 0) { + setState(state + 1); + } + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts new file mode 100644 index 0000000000..df025778ca --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/CompilerError'; +import {ValidateSetStateInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in render rule', ValidateSetStateInRenderRule, { + valid: [], + invalid: [ + { + name: 'setState in useMemo', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + return item; + }, [cond, item, init]); + + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'unconditional setState in render', + code: normalizeIndent` + function Component(props) { + const [x, setX] = useState(0); + const aliased = setX; + + setX(1); + aliased(2); + + return x; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts new file mode 100644 index 0000000000..77f6dd93fb --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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 {NoUnusedDirectivesRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule} from './shared-utils'; + +testRule('no unused directives rule', NoUnusedDirectivesRule, { + valid: [], + invalid: [ + { + name: "Unused 'use no forget' directive is reported when no errors are present on components", + code: normalizeIndent` + function Component() { + 'use no forget'; + return
Hello world
+ } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction Component() {\n \n return
Hello world
\n}\n', + }, + ], + }, + ], + }, + + { + name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", + code: normalizeIndent` + function notacomponent() { + 'use no forget'; + return 1 + 1; + } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', + }, + ], + }, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts new file mode 100644 index 0000000000..b5700725f2 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts @@ -0,0 +1,172 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + testRule, + makeTestCaseError, + TestRecommendedRules, +} from './shared-utils'; +import {ErrorCode} from 'babel-plugin-react-compiler/src/CompilerError'; + +testRule('plugin-recommended', TestRecommendedRules, { + valid: [ + { + name: 'Basic example with component syntax', + code: normalizeIndent` + export default component HelloWorld( + text: string = 'Hello!', + onClick: () => void, + ) { + return
{text}
; + } + `, + }, + + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Multiple diagnostic kinds from the same function are surfaced', + code: normalizeIndent` + import Child from './Child'; + function Component() { + const result = cond ?? useConditionalHook(); + return <> + {Child(result)} + ; + } + `, + errors: [ + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + { + name: 'Multiple diagnostics within the same file are surfaced', + code: normalizeIndent` + function useConditional1() { + 'use memo'; + return cond ?? useConditionalHook(); + } + function useConditional2(props) { + 'use memo'; + return props.cond && useConditionalHook(); + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + { + name: "'use no forget' does not disable eslint rule", + code: normalizeIndent` + let count = 0; + function Component() { + 'use no forget'; + return cond ?? useConditionalHook(); + + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple non-fatal useMemo diagnostics are surfaced', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + }, [cond, item, init]); + + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'Pipeline errors are reported', + code: normalizeIndent` + import useMyEffect from 'useMyEffect'; + import {AUTODEPS} from 'react'; + function Component({a}) { + 'use no memo'; + useMyEffect(() => console.log(a.b), AUTODEPS); + return
Hello world
; + } + `, + options: [ + { + environment: { + inferEffectDependencies: [ + { + function: { + source: 'useMyEffect', + importSpecifierName: 'default', + }, + autodepsIndex: 1, + }, + ], + }, + }, + ], + errors: [ + { + message: /Cannot infer dependencies of this effect/, + }, + ], + }, + ], +}); + +testRule('rules that are not enabled do not error', StaticComponentsRule, { + valid: [ + { + name: 'simple case', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + }, + ], + invalid: [], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts deleted file mode 100644 index bff40e9649..0000000000 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts +++ /dev/null @@ -1,287 +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 {ErrorSeverity} from 'babel-plugin-react-compiler/src'; -import {RuleTester as ESLintTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)![0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: ESLintTester.ValidTestCase[]; - invalid: ESLintTester.InvalidTestCase[]; -}; - -const tests: CompilerTestCases = { - valid: [ - { - name: 'Basic example', - code: normalizeIndent` - function foo(x, y) { - if (x) { - return foo(false, y); - } - return [y * 10]; - } - `, - }, - { - name: 'Violation with Flow suppression', - code: ` - // Valid since error already suppressed with flow. - function useHookWithHook() { - if (cond) { - // $FlowFixMe[react-rule-hook] - useConditionalHook(); - } - } - `, - }, - { - name: 'Basic example with component syntax', - code: normalizeIndent` - export default component HelloWorld( - text: string = 'Hello!', - onClick: () => void, - ) { - return
{text}
; - } - `, - }, - { - name: 'Unsupported syntax', - code: normalizeIndent` - function foo(x) { - var y = 1; - return y * x; - } - `, - }, - { - // OK because invariants are only meant for the compiler team's consumption - name: '[Invariant] Defined after use', - code: normalizeIndent` - function Component(props) { - let y = function () { - m(x); - }; - - let x = { a }; - m(x); - return y; - } - `, - }, - { - name: "Classes don't throw", - code: normalizeIndent` - class Foo { - #bar() {} - } - `, - }, - ], - invalid: [ - { - name: 'Reportable levels can be configured', - options: [{reportableLevels: new Set([ErrorSeverity.Todo])}], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: '[InvalidReact] ESlint suppression', - // Indentation is intentionally weird so it doesn't add extra whitespace - code: normalizeIndent` - function Component(props) { - // eslint-disable-next-line react-hooks/rules-of-hooks - return
{props.foo}
; - }`, - errors: [ - { - message: /React Compiler has skipped optimizing this component/, - suggestions: [ - { - output: normalizeIndent` - function Component(props) { - - return
{props.foo}
; - }`, - }, - ], - }, - { - message: - "Definition for rule 'react-hooks/rules-of-hooks' was not found.", - }, - ], - }, - { - name: 'Multiple diagnostics are surfaced', - options: [ - { - reportableLevels: new Set([ - ErrorSeverity.Todo, - ErrorSeverity.InvalidReact, - ]), - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - } - function Bar(props) { - props.a.b = 2; - return
{props.c}
- }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - { - message: /Modifying component props or hook arguments is not allowed/, - }, - ], - }, - { - name: 'Test experimental/unstable report all bailouts mode', - options: [ - { - reportableLevels: new Set([ErrorSeverity.InvalidReact]), - __unstable_donotuse_reportAllBailouts: true, - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: "'use no forget' does not disable eslint rule", - code: normalizeIndent` - let count = 0; - function Component() { - 'use no forget'; - count = count + 1; - return
Hello world {count}
- } - `, - errors: [ - { - message: - /Cannot reassign variables declared outside of the component\/hook/, - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on components", - code: normalizeIndent` - function Component() { - 'use no forget'; - return
Hello world
- } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction Component() {\n \n return
Hello world
\n}\n', - }, - ], - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", - code: normalizeIndent` - function notacomponent() { - 'use no forget'; - return 1 + 1; - } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', - }, - ], - }, - ], - }, - { - name: 'Pipeline errors are reported', - code: normalizeIndent` - import useMyEffect from 'useMyEffect'; - import {AUTODEPS} from 'react'; - function Component({a}) { - 'use no memo'; - useMyEffect(() => console.log(a.b), AUTODEPS); - return
Hello world
; - } - `, - options: [ - { - environment: { - inferEffectDependencies: [ - { - function: { - source: 'useMyEffect', - importSpecifierName: 'default', - }, - autodepsIndex: 1, - }, - ], - }, - }, - ], - errors: [ - { - message: /Cannot infer dependencies of this effect/, - }, - ], - }, - ], -}; - -const eslintTester = new ESLintTester({ - parser: require.resolve('hermes-eslint'), - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, -}); -eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts index 5a2bea6852..87baf724e1 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts @@ -6,22 +6,11 @@ */ import {RuleTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)[0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: RuleTester.ValidTestCase[]; - invalid: RuleTester.InvalidTestCase[]; -}; +import { + CompilerTestCases, + normalizeIndent, + TestRecommendedRules, +} from './shared-utils'; const tests: CompilerTestCases = { valid: [ @@ -70,6 +59,7 @@ const tests: CompilerTestCases = { }; const eslintTester = new RuleTester({ + // @ts-ignore[2353] - outdated types parser: require.resolve('@typescript-eslint/parser'), }); -eslintTester.run('react-compiler', ReactCompilerRule, tests); +eslintTester.run('react-compiler', TestRecommendedRules, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts new file mode 100644 index 0000000000..b2f4b54994 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts @@ -0,0 +1,44 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidStaticComponentsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('static-components', StaticComponentsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function Example(props) { + const Component = new ComponentFactory(); + return ; + } + `, + errors: [invalidStaticComponentsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function Example(props) { + const Component1 = new ComponentFactory(); + const Component2 = new ComponentFactory(); + return <> + + + ; + }`, + errors: [ + invalidStaticComponentsRuleErrorMessage, + invalidStaticComponentsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts new file mode 100644 index 0000000000..445dace3b5 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts @@ -0,0 +1,36 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/CompilerError'; +import {UnnecessaryEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('unnecessary effects rule', UnnecessaryEffectsRule, { + valid: [], + invalid: [ + { + name: 'test case from React docs', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + // https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state + function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + + // 🔴 Avoid: redundant state and unnecessary Effect + const [fullName, setFullName] = useState(''); + useEffect(() => { + setFullName(capitalize(firstName + ' ' + lastName)); + }, [firstName, lastName]); + + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts new file mode 100644 index 0000000000..97dbd10f00 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts @@ -0,0 +1,43 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/CompilerError'; +import {UseMemoRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('use memo rule', UseMemoRule, { + valid: [], + invalid: [ + { + name: 'Simple async violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component({a, b}) { + let x = useMemo(async () => { + await a; + }, []); + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC)], + }, + { + name: 'Simple parameters violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component() { + let x = useMemo(c => a, []); + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts new file mode 100644 index 0000000000..a0b4beff77 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts @@ -0,0 +1,98 @@ +import {RuleTester as ESLintTester, Rule} from 'eslint'; +import { + ErrorCode, + ErrorCodeDetails, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import escape from 'regexp.escape'; +import {configs} from '../src/index'; + +/** + * A string template tag that removes padding from the left side of multi-line strings + * @param {Array} strings array of code strings (only one expected) + */ +export function normalizeIndent(strings: TemplateStringsArray): string { + const codeLines = strings[0].split('\n'); + const leftPadding = codeLines[1].match(/\s+/)![0]; + return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); +} + +export type CompilerTestCases = { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; +}; + +export const invalidHookRuleErrorMessage: ESLintTester.TestCaseError = { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason), + ), +}; + +export const invalidCapitalizedCallsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.CAPITALIZED_CALLS].reason), + ), + }; + +export const invalidStaticComponentsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.STATIC_COMPONENTS].reason), + ), + }; + +export function makeTestCaseError(code: ErrorCode): ESLintTester.TestCaseError { + return { + message: new RegExp(escape(ErrorCodeDetails[code].reason)), + }; +} + +export function testRule( + name: string, + rule: Rule.RuleModule, + tests: { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; + }, +): void { + const eslintTester = new ESLintTester({ + // @ts-ignore[2353] - outdated types + parser: require.resolve('hermes-eslint'), + parserOptions: { + ecmaVersion: 2015, + sourceType: 'module', + enableExperimentalComponentSyntax: true, + }, + }); + + eslintTester.run(name, rule, tests); +} + +/** + * Aggregates all recommended rules from the plugin. + */ +export const TestRecommendedRules: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Disallow capitalized function calls', + category: 'Possible Errors', + recommended: true, + }, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context) { + for (const rule of Object.values( + configs.recommended.plugins['react-compiler'].rules, + )) { + const listener = rule.create(context); + if (Object.entries(listener).length !== 0) { + throw new Error('TODO: handle rules that return listeners to eslint'); + } + } + return {}; + }, +}; + +test('no test', () => {}); diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index 2dd191f033..e5402611e2 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -24,11 +24,13 @@ "@babel/preset-typescript": "^7.18.6", "@babel/types": "^7.26.0", "@types/eslint": "^8.56.12", + "@types/jest": "^30.0.0", "@types/node": "^20.2.5", "babel-jest": "^29.0.3", "eslint": "8.57.0", "hermes-eslint": "^0.25.1", - "jest": "^29.5.0" + "jest": "^29.5.0", + "regexp.escape": "^2.0.1" }, "engines": { "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" diff --git a/compiler/packages/eslint-plugin-react-compiler/src/index.ts b/compiler/packages/eslint-plugin-react-compiler/src/index.ts index a3577a101e..681e9844ef 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/index.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/index.ts @@ -5,29 +5,61 @@ * LICENSE file in the root directory of this source tree. */ -import ReactCompilerRule from './rules/ReactCompilerRule'; +import * as rules from './rules/ReactCompilerRule'; const meta = { name: 'eslint-plugin-react-compiler', }; -const rules = { - 'react-compiler': ReactCompilerRule, +/** + * Validates React components and hooks follow rules of React + * and follow best practices + */ +const validationRules = { + 'rules-of-hooks': rules.RulesOfHooksRule, + 'no-capitalized-calls': rules.NoCapitalizedCallsRule, + 'no-unstable-components': rules.StaticComponentsRule, + 'validate-use-memo-callbacks': rules.UseMemoRule, + 'validate-writes': rules.InvalidWritesRule, + 'no-unsafe-refs': rules.UnsafeRefsRule, + 'validate-set-state-in-render': rules.ValidateSetStateInRenderRule, + 'no-set-state-in-effects': rules.NoSetStateInEffectsRule, + 'no-ref-access-in-render': rules.NoRefAccessInRenderRule, + 'no-impure-function-calls': rules.NoImpureFunctionCallsRule, + 'unnecessary-effects': rules.UnnecessaryEffectsRule, + 'no-ambiguous-jsx': rules.NoAmbiguousJsxRule, +}; + +const recommendedRules = { + ...validationRules, + 'no-unsupported-syntax': rules.NoUnsupportedSyntaxRule, + + /** Validation for React Compiler inline configuration */ + 'no-unused-directives': rules.NoUnusedDirectivesRule, + 'validate-compiler-config': rules.ValidateCompilerConfigRule, +}; + +const allRules = { + ...recommendedRules, + /** Warn on syntax that React Compiler cannot transform */ + 'no-todo-syntax': rules.WarnOnTodoSyntaxRule, + 'warn-on-compilation-failures': rules.WarnOnUnactionableFailuresRule, }; const configs = { recommended: { plugins: { 'react-compiler': { - rules: { - 'react-compiler': ReactCompilerRule, - }, + rules: recommendedRules, }, }, - rules: { - 'react-compiler/react-compiler': 'error' as const, - }, + rules: Object.fromEntries( + Object.keys(recommendedRules).map(ruleName => [ + 'react-compiler/' + ruleName, + 'error', + ]), + ) as Record, }, }; -export {configs, rules, meta}; +export {configs, allRules as rules, meta}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index 730b6ff6f8..0058464090 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -5,43 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import {transformFromAstSync} from '@babel/core'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import type {SourceLocation as BabelSourceLocation} from '@babel/types'; -import BabelPluginReactCompiler, { - CompilerDiagnostic, +import { CompilerDiagnosticOptions, - CompilerErrorDetail, CompilerErrorDetailOptions, CompilerSuggestionOperation, - ErrorSeverity, - parsePluginOptions, - validateEnvironmentConfig, - OPT_OUT_DIRECTIVES, - type PluginOptions, } from 'babel-plugin-react-compiler/src'; -import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Rule} from 'eslint'; -import {Statement} from 'estree'; -import * as HermesParser from 'hermes-parser'; +import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler'; +import {LinterCategory} from 'babel-plugin-react-compiler/src/CompilerError'; function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } -const DEFAULT_REPORTABLE_LEVELS = new Set([ - ErrorSeverity.InvalidReact, - ErrorSeverity.InvalidJS, -]); -let reportableLevels = DEFAULT_REPORTABLE_LEVELS; - -function isReportableDiagnostic( - detail: CompilerErrorDetail | CompilerDiagnostic, -): boolean { - return reportableLevels.has(detail.severity); -} - function makeSuggestions( detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array { @@ -95,28 +72,97 @@ function makeSuggestions( return suggest; } -const COMPILER_OPTIONS: Partial = { - noEmit: true, - panicThreshold: 'none', - // Don't emit errors on Flow suppressions--Flow already gave a signal - flowSuppressions: false, - environment: validateEnvironmentConfig({ - validateRefAccessDuringRender: true, - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - }), -}; +function getReactCompilerResult(context: Rule.RuleContext): RunCacheEntry { + // Compat with older versions of eslint + const sourceCode = context.sourceCode ?? context.getSourceCode(); + const filename = context.filename ?? context.getFilename(); + const userOpts = context.options[0] ?? {}; -const rule: Rule.RuleModule = { + const results = runReactCompiler({ + sourceCode, + filename, + userOpts, + }); + + return results; +} + +function hasFlowSuppression( + program: RunCacheEntry, + nodeLoc: BabelSourceLocation, + suppressions: Array, +): boolean { + for (const commentNode of program.flowSuppressions) { + if ( + suppressions.includes(commentNode.code) && + commentNode.line === nodeLoc.start.line - 1 + ) { + return true; + } + } + return false; +} + +function makeRule(filter: Array): Rule.RuleModule['create'] { + return (context: Rule.RuleContext): Rule.RuleListener => { + const result = getReactCompilerResult(context); + + for (const event of result.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if ( + detail.linterCategory != null && + filter.includes(detail.linterCategory) + ) { + const loc = detail.primaryLocation(); + if (loc == null || typeof loc === 'symbol') { + continue; + } + if ( + hasFlowSuppression(result, loc, [ + 'react-rule-hook', + 'react-rule-unsafe-ref', + ]) + ) { + // If Flow already caught this error, we don't need to report it again. + continue; + } + // TODO: if multiple rules report the same linter category, + // we should deduplicate them with a "reported" set + context.report({ + message: detail.printErrorMessage(result.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); + } + } + } + return {}; + }; +} + +const unactionableBailouts: Rule.RuleModule = { meta: { type: 'problem', docs: { - description: 'Surfaces diagnostics from React Forget', + description: 'Surfaces unactionable compilation bailouts', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + }, + create(context: Rule.RuleContext): Rule.RuleListener { + return {}; + }, +}; + +export const RulesOfHooksRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to the rules of hooks', recommended: true, }, fixable: 'code', @@ -124,234 +170,284 @@ const rule: Rule.RuleModule = { // validation is done at runtime with zod schema: [{type: 'object', additionalProperties: true}], }, - create(context: Rule.RuleContext) { - // Compat with older versions of eslint - const sourceCode = context.sourceCode ?? context.getSourceCode(); - const filename = context.filename ?? context.getFilename(); - const userOpts = context.options[0] ?? {}; - if ( - userOpts.reportableLevels != null && - userOpts.reportableLevels instanceof Set - ) { - reportableLevels = userOpts.reportableLevels; - } else { - reportableLevels = DEFAULT_REPORTABLE_LEVELS; - } - /** - * Experimental setting to report all compilation bailouts on the compilation - * unit (e.g. function or hook) instead of the offensive line. - * Intended to be used when a codebase is 100% reliant on the compiler for - * memoization (i.e. deleted all manual memo) and needs compilation success - * signals for perf debugging. - */ - let __unstable_donotuse_reportAllBailouts: boolean = false; - if ( - userOpts.__unstable_donotuse_reportAllBailouts != null && - typeof userOpts.__unstable_donotuse_reportAllBailouts === 'boolean' - ) { - __unstable_donotuse_reportAllBailouts = - userOpts.__unstable_donotuse_reportAllBailouts; - } + create: makeRule([LinterCategory.RULES_OF_HOOKS]), +}; - let shouldReportUnusedOptOutDirective = true; - const options: PluginOptions = parsePluginOptions({ - ...COMPILER_OPTIONS, - ...userOpts, - environment: { - ...COMPILER_OPTIONS.environment, - ...userOpts.environment, - }, - }); - const userLogger: Logger | null = options.logger; - options.logger = { - logEvent: (eventFilename, event): void => { - userLogger?.logEvent(eventFilename, event); - if (event.kind === 'CompileError') { - shouldReportUnusedOptOutDirective = false; - const detail = event.detail; - const suggest = makeSuggestions(detail.options); - if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) { - const loc = detail.primaryLocation(); - const locStr = - loc != null && typeof loc !== 'symbol' - ? ` (@:${loc.start.line}:${loc.start.column})` - : ''; - /** - * Report bailouts with a smaller span (just the first line). - * Compiler bailout lints only serve to flag that a react function - * has not been optimized by the compiler for codebases which depend - * on compiler memo heavily for perf. These lints are also often not - * actionable. - */ - let endLoc; - if (event.fnLoc.end.line === event.fnLoc.start.line) { - endLoc = event.fnLoc.end; - } else { - endLoc = { - line: event.fnLoc.start.line, - // Babel loc line numbers are 1-indexed - column: - sourceCode.text.split(/\r?\n|\r|\n/g)[ - event.fnLoc.start.line - 1 - ]?.length ?? 0, - }; - } - const firstLineLoc = { - start: event.fnLoc.start, - end: endLoc, - }; - context.report({ - message: `${detail.printErrorMessage(sourceCode.text, {eslint: true})} ${locStr}`, - loc: firstLineLoc, - suggest, - }); - } +export const NoCapitalizedCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to capitalized calls', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.CAPITALIZED_CALLS]), +}; +export const StaticComponentsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'todo', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.STATIC_COMPONENTS]), +}; + +export const InvalidWritesRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.INVALID_WRITE]), +}; +export const UseMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Surfaces compilation errors related to invalid useMemo usage', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.VALIDATE_MANUAL_MEMO]), +}; + +// TODO: test cases +export const NoDynamicManualMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.DYNAMIC_MANUAL_MEMO]), +}; + +export const UnsafeRefsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to unsafe refs', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.EXHAUSTIVE_DEPS]), +}; + +export const ValidateSetStateInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_RENDER]), +}; + +export const NoSetStateInEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to setState in render', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_EFFECTS]), +}; + +export const NoRefAccessInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_REF_ACCESS_IN_RENDER]), +}; + +export const NoImpureFunctionCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.IMPURE_FUNCTIONS]), +}; + +export const UnnecessaryEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNNECESSARY_EFFECTS]), +}; + +export const NoAmbiguousJsxRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on JSX usage that is ambiguous.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.JSX_IN_TRY]), +}; + +// TODO: test cases +export const NoUnsupportedSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler does not and will not support.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNSUPPORTED_SYNTAX]), +}; + +// TODO: test cases +export const WarnOnTodoSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler currently does not support, but may in the future.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.TODO_SYNTAX]), +}; + +// TODO: test cases +export const ValidateCompilerConfigRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Validates the React Compiler configuration', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.COMPILER_CONFIG]), +}; + +export const WarnOnUnactionableFailuresRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on compilation failures that are not actionable', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const event of results.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if (detail.linterCategory == null) { const loc = detail.primaryLocation(); - if ( - !isReportableDiagnostic(detail) || - loc == null || - typeof loc === 'symbol' - ) { - return; + if (loc == null || typeof loc === 'symbol') { + continue; } - if ( - hasFlowSuppression(loc, 'react-rule-hook') || - hasFlowSuppression(loc, 'react-rule-unsafe-ref') - ) { - // If Flow already caught this error, we don't need to report it again. - return; - } - if (loc != null) { - context.report({ - message: detail.printErrorMessage(sourceCode.text, { - eslint: true, - }), - loc, - suggest, - }); - } - } - }, - }; - - try { - options.environment = validateEnvironmentConfig( - options.environment ?? {}, - ); - } catch (err: unknown) { - options.logger?.logEvent('', err as LoggerEvent); - } - - function hasFlowSuppression( - nodeLoc: BabelSourceLocation, - suppression: string, - ): boolean { - const comments = sourceCode.getAllComments(); - const flowSuppressionRegex = new RegExp( - '\\$FlowFixMe\\[' + suppression + '\\]', - ); - for (const commentNode of comments) { - if ( - flowSuppressionRegex.test(commentNode.value) && - commentNode.loc!.end.line === nodeLoc.start.line - 1 - ) { - return true; + context.report({ + message: detail.printErrorMessage(results.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); } } - return false; - } - - let babelAST; - if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { - try { - const {parse: babelParse} = require('@babel/parser'); - babelAST = babelParse(sourceCode.text, { - filename, - sourceType: 'unambiguous', - plugins: ['typescript', 'jsx'], - }); - } catch { - /* empty */ - } - } else { - try { - babelAST = HermesParser.parse(sourceCode.text, { - babel: true, - enableExperimentalComponentSyntax: true, - sourceFilename: filename, - sourceType: 'module', - }); - } catch { - /* empty */ - } - } - - if (babelAST != null) { - try { - transformFromAstSync(babelAST, sourceCode.text, { - filename, - highlightCode: false, - retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], - sourceType: 'module', - configFile: false, - babelrc: false, - }); - } catch (err) { - /* errors handled by injected logger */ - } - } - - function reportUnusedOptOutDirective(stmt: Statement) { - if ( - stmt.type === 'ExpressionStatement' && - stmt.expression.type === 'Literal' && - typeof stmt.expression.value === 'string' && - OPT_OUT_DIRECTIVES.has(stmt.expression.value) && - stmt.loc != null - ) { - context.report({ - message: `Unused '${stmt.expression.value}' directive`, - loc: stmt.loc, - suggest: [ - { - desc: 'Remove the directive', - fix(fixer) { - return fixer.remove(stmt); - }, - }, - ], - }); - } - } - if (shouldReportUnusedOptOutDirective) { - return { - FunctionDeclaration(fnDecl) { - for (const stmt of fnDecl.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - ArrowFunctionExpression(fnExpr) { - if (fnExpr.body.type === 'BlockStatement') { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - } - }, - FunctionExpression(fnExpr) { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - }; - } else { - return {}; } + return {}; }, }; -export default rule; +export const NoUnusedDirectivesRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const directive of results.unusedOptOutDirectives) { + context.report({ + message: `Unused '${directive.directive}' directive`, + loc: directive.loc, + suggest: [ + { + desc: 'Remove the directive', + fix(fixer) { + return fixer.removeRange(directive.range); + }, + }, + ], + }); + } + return {}; + }, +}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts new file mode 100644 index 0000000000..655eaf5197 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -0,0 +1,323 @@ +/** + * 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 {transformFromAstSync, traverse} from '@babel/core'; +import {parse as babelParse} from '@babel/parser'; +import {Directive, File} from '@babel/types'; +// @ts-expect-error: no types available +import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; +import BabelPluginReactCompiler, { + parsePluginOptions, + validateEnvironmentConfig, + OPT_OUT_DIRECTIVES, + type PluginOptions, +} from 'babel-plugin-react-compiler/src'; +import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; +import type {SourceCode} from 'eslint'; +import {SourceLocation} from 'estree'; +// @ts-expect-error: no types available +import * as HermesParser from 'hermes-parser'; +import {isDeepStrictEqual} from 'util'; +import type {ParseResult} from '@babel/parser'; + +const COMPILER_OPTIONS: Partial = { + noEmit: true, + panicThreshold: 'none', + // Don't emit errors on Flow suppressions--Flow already gave a signal + flowSuppressions: false, + environment: validateEnvironmentConfig({ + validateRefAccessDuringRender: true, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + // TODO: remove, this should be in the type system + validateNoCapitalizedCalls: [], + validateHooksUsage: true, + validateNoDerivedComputationsInEffects: true, + }), +}; + +export type UnusedOptOutDirective = { + loc: SourceLocation; + range: [number, number]; + directive: string; +}; +export type RunCacheEntry = { + sourceCode: string; + filename: string; + userOpts: PluginOptions; + flowSuppressions: Array<{line: number; code: string}>; + unusedOptOutDirectives: Array; + events: LoggerEvent[]; +}; + +type RunParams = { + sourceCode: SourceCode; + filename: string; + userOpts: PluginOptions; +}; +const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g; + +function getFlowSuppressions( + sourceCode: SourceCode, +): Array<{line: number; code: string}> { + const comments = sourceCode.getAllComments(); + const results: Array<{line: number; code: string}> = []; + + for (const commentNode of comments) { + const matches = commentNode.value.matchAll(FLOW_SUPPRESSION_REGEX); + for (const match of matches) { + if (match.index != null && commentNode.loc != null) { + const code = match[1]; + results.push({ + line: commentNode.loc!.end.line, + code, + }); + } + } + } + return results; +} + +function filterUnusedOptOutDirectives( + directives: ReadonlyArray, +): Array { + const results: Array = []; + for (const directive of directives) { + if ( + OPT_OUT_DIRECTIVES.has(directive.value.value) && + directive.loc != null + ) { + results.push({ + loc: directive.loc, + directive: directive.value.value, + range: [directive.start!, directive.end!], + }); + } + } + return results; +} + +function runReactCompilerImpl({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + // Compat with older versions of eslint + for (const [key, entry] of Object.entries(userOpts)) { + if (key === 'environment' && COMPILER_OPTIONS.environment != null) { + for (const envKey of Object.keys(entry as Record)) { + if ( + COMPILER_OPTIONS.environment.hasOwnProperty(envKey) && + isDeepStrictEqual( + (entry as Record)[envKey], + (COMPILER_OPTIONS.environment as Record)[envKey], + ) + ) { + console.warn('Conflicting environment option detected: ' + envKey); + } + } + } else if (COMPILER_OPTIONS.hasOwnProperty(key)) { + if (isDeepStrictEqual(entry, (COMPILER_OPTIONS as any)[key])) { + console.warn('Conflicting option detected: ' + key); + } + } + } + const options: PluginOptions = parsePluginOptions({ + ...COMPILER_OPTIONS, + ...userOpts, + environment: { + ...COMPILER_OPTIONS.environment, + ...userOpts.environment, + }, + }); + const results: RunCacheEntry = { + sourceCode: sourceCode.text, + filename, + userOpts, + flowSuppressions: [], + unusedOptOutDirectives: [], + events: [], + }; + const userLogger: Logger | null = options.logger; + options.logger = { + logEvent: (eventFilename, event): void => { + userLogger?.logEvent(eventFilename, event); + results.events.push(event); + }, + }; + + try { + options.environment = validateEnvironmentConfig(options.environment ?? {}); + } catch (err: unknown) { + options.logger?.logEvent(filename, err as LoggerEvent); + } + + let babelAST: ParseResult | null = null; + if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { + try { + babelAST = babelParse(sourceCode.text, { + sourceFilename: filename, + sourceType: 'unambiguous', + plugins: ['typescript', 'jsx'], + }); + } catch { + /* empty */ + } + } else { + try { + babelAST = HermesParser.parse(sourceCode.text, { + babel: true, + enableExperimentalComponentSyntax: true, + sourceFilename: filename, + sourceType: 'module', + }); + } catch { + /* empty */ + } + } + + if (babelAST != null) { + results.flowSuppressions = getFlowSuppressions(sourceCode); + try { + transformFromAstSync(babelAST, sourceCode.text, { + filename, + highlightCode: false, + retainLines: true, + plugins: [ + [PluginProposalPrivateMethods, {loose: true}], + [BabelPluginReactCompiler, options], + ], + sourceType: 'module', + configFile: false, + babelrc: false, + }); + + if (results.events.filter(e => e.kind === 'CompileError').length === 0) { + traverse(babelAST, { + FunctionDeclaration(path) { + path.node; + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + ArrowFunctionExpression(path) { + if (path.node.body.type === 'BlockStatement') { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + } + }, + FunctionExpression(path) { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + }); + } + } catch (err) { + /* errors handled by injected logger */ + } + } + + return results; +} + +const SENTINEL = Symbol(); + +type T = {[k: string]: any}; + +// Array backed LRU cache -- should be small < 10 elements +class LRUCache { + // newest at headIdx, then headIdx + 1, ..., tailIdx + #values: Array<[K, T | Error] | [typeof SENTINEL, void]>; + #headIdx: number = 0; + // #store: (key: K) => T | Error; + // #isValue: null | ((key: T | Error) => key is T); + + constructor( + size: number, + // store: (key: K) => T | Error, + // isValue?: (key: T | Error) => key is T, + ) { + this.#values = new Array(size).fill(SENTINEL); + // this.#store = store; + // this.#isValue = isValue ?? null; + } + + // gets a value and sets it as "recently used" + get(key: K): T | null { + let idx = this.#values.findIndex(entry => entry[0] === key); + // If found, move to front + if (idx === this.#headIdx) { + return this.#values[this.#headIdx][1] as T; + } else if (idx < 0) { + return null; + // const value = this.#store(key); + // if (this.#isValue && !this.#isValue(value)) { + // return value; // Return error directly + // } + // this.#headIdx = + // (this.#headIdx - 1 + this.#values.length) % this.#values.length; + // this.#values[this.#headIdx] = [key, value]; + } + + const entry: [K, T] = this.#values[idx] as [K, T]; + + const len = this.#values.length; + for (let i = 0; i < Math.min(idx, len - 1); i++) { + this.#values[(this.#headIdx + i + 1) % len] = + this.#values[(this.#headIdx + i) % len]; + } + this.#values[this.#headIdx] = entry; + return entry[1]; + } + push(key: K, value: T): void { + this.#headIdx = + (this.#headIdx - 1 + this.#values.length) % this.#values.length; + this.#values[this.#headIdx] = [key, value]; + } +} +const cache = new LRUCache(10); + +export default function runReactCompiler({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + const entry = cache.get(filename); + if ( + entry != null && + entry.sourceCode === sourceCode.text && + isDeepStrictEqual(entry.userOpts, userOpts) + ) { + return entry; + } else if (entry != null) { + if (process.env['DEBUG']) { + console.log( + `Cache hit for ${filename}, but source code or options changed, recomputing`, + ); + } + } + + const runEntry = runReactCompilerImpl({ + sourceCode, + filename, + userOpts, + }); + // If we have a cache entry, we can update it + if (entry != null) { + Object.assign(entry, runEntry); + } else { + cache.push(filename, runEntry); + } + return {...runEntry}; +} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index a159359773..a6041bd5cc 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -338,7 +338,16 @@ export async function transformFixtureInput( if (logs.length !== 0) { formattedLogs = logs .map(({event}) => { - return JSON.stringify(event); + return JSON.stringify(event, (key, value) => { + if ( + key === 'detail' && + value != null && + typeof value.serialize === 'function' + ) { + return value.serialize(); + } + return value; + }); }) .join('\n'); } diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 6e1bc7feeb..696261cbf5 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -542,11 +542,6 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - "@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" @@ -1605,7 +1600,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": +"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.27.0", "@babel/types@^7.27.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== @@ -1613,14 +1608,6 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -2148,6 +2135,11 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/diff-sequences@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + "@jest/environment@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz" @@ -2178,6 +2170,13 @@ "@types/node" "*" jest-mock "^29.5.0" +"@jest/expect-utils@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz#9d42e4b8bc80367db30abc6c42b2cb14073f66fc" + integrity sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew== + dependencies: + "@jest/get-type" "30.0.1" + "@jest/expect-utils@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz" @@ -2274,6 +2273,11 @@ jest-mock "^29.5.0" jest-util "^29.5.0" +"@jest/get-type@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.0.1.tgz#0d32f1bbfba511948ad247ab01b9007724fc9f52" + integrity sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw== + "@jest/globals@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz" @@ -2313,6 +2317,14 @@ "@jest/types" "^29.6.3" jest-mock "^29.7.0" +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + "@jest/reporters@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz" @@ -2435,6 +2447,13 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/schemas@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" @@ -2663,6 +2682,19 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz" @@ -2965,6 +2997,11 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sinclair/typebox@^0.34.0": + version "0.34.38" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.38.tgz#2365df7c23406a4d79413a766567bfbca708b49d" + integrity sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" @@ -3154,6 +3191,11 @@ resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== +"@types/istanbul-lib-coverage@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + "@types/istanbul-lib-report@*": version "3.0.0" resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" @@ -3176,6 +3218,13 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jest@^28.1.6": version "28.1.8" resolved "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz" @@ -3200,6 +3249,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + "@types/jsdom@^20.0.0": version "20.0.0" resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz" @@ -3282,6 +3339,11 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz" @@ -3309,6 +3371,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.33": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz" @@ -3740,7 +3809,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: +ansi-styles@^5.0.0, ansi-styles@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== @@ -3803,11 +3872,32 @@ aria-query@^5.0.0: resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.0.2.tgz" integrity sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q== +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + ast-types@^0.13.4: version "0.13.4" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz" @@ -3815,6 +3905,11 @@ ast-types@^0.13.4: dependencies: tslib "^2.0.1" +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + async@^3.2.3: version "3.2.6" resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" @@ -3825,6 +3920,13 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + axios@^1.6.1: version "1.7.4" resolved "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz" @@ -4245,7 +4347,7 @@ cac@^6.7.14: resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== @@ -4253,7 +4355,17 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -call-bound@^1.0.2: +call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== @@ -4295,7 +4407,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: +chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4377,6 +4489,11 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz" integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== +ci-info@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" @@ -4722,6 +4839,33 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + date-fns@^2.29.1: version "2.30.0" resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz" @@ -4788,6 +4932,24 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + degenerator@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz" @@ -4922,7 +5084,7 @@ dreamopt@~0.6.0: dependencies: wordwrap ">=0.0.2" -dunder-proto@^1.0.1: +dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== @@ -5033,7 +5195,67 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.1: +es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.9: + version "1.24.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" + integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== @@ -5050,6 +5272,25 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" + es5-ext@0.8.x: version "0.8.2" resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz" @@ -5428,6 +5669,18 @@ expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expect@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.0.5.tgz#c23bf193c5e422a742bfd2990ad990811de41a5a" + integrity sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ== + dependencies: + "@jest/expect-utils" "30.0.5" + "@jest/get-type" "30.0.1" + jest-matcher-utils "30.0.5" + jest-message-util "30.0.5" + jest-mock "30.0.5" + jest-util "30.0.5" + express-rate-limit@^7.5.0: version "7.5.0" resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz" @@ -5719,6 +5972,13 @@ follow-redirects@^1.15.6: resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz" @@ -5788,6 +6048,23 @@ function-bind@^1.1.2: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -5798,7 +6075,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -5819,7 +6096,7 @@ get-package-type@^0.1.0: resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proto@^1.0.1: +get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -5839,6 +6116,15 @@ get-stream@^6.0.0: resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + get-uri@^6.0.1: version "6.0.4" resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz" @@ -5957,6 +6243,14 @@ globals@^14.0.0: resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" @@ -5969,12 +6263,12 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.2.0: +gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -5989,6 +6283,11 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" @@ -5999,11 +6298,32 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.1.0: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" @@ -6231,6 +6551,15 @@ ini@^1.3.4: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" @@ -6251,6 +6580,15 @@ ipaddr.js@1.9.1: resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -6261,6 +6599,24 @@ is-arrayish@^0.3.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" @@ -6268,6 +6624,19 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-core-module@^2.9.0: version "2.10.0" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" @@ -6275,11 +6644,35 @@ is-core-module@^2.9.0: dependencies: has "^1.0.3" +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" @@ -6290,6 +6683,16 @@ is-generator-fn@^2.0.0: resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== + dependencies: + call-bound "^1.0.3" + get-proto "^1.0.0" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -6307,6 +6710,24 @@ is-interactive@^2.0.0: resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" @@ -6339,11 +6760,57 @@ is-promise@^4.0.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz" integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" @@ -6354,11 +6821,36 @@ is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0: resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2, is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-windows@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -6797,6 +7289,16 @@ jest-config@^29.7.0: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-diff@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.5.tgz#b40f81e0c0d13e5b81c4d62b0d0dfa6a524ee0fd" + integrity sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + pretty-format "30.0.5" + jest-diff@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz" @@ -7106,6 +7608,16 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-matcher-utils@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz#dff3334be58faea4a5e1becc228656fbbfc2467d" + integrity sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ== + dependencies: + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + jest-diff "30.0.5" + pretty-format "30.0.5" + jest-matcher-utils@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz" @@ -7146,6 +7658,21 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-message-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.5.tgz#dd12ffec91dd3fa6a59cbd538a513d8e239e070c" + integrity sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-message-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz" @@ -7206,6 +7733,15 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + jest-util "30.0.5" + jest-mock@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz" @@ -7237,6 +7773,11 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + jest-regex-util@^28.0.2: version "28.0.2" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" @@ -7683,6 +8224,18 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + jest-util@^28.0.0, jest-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" @@ -8327,7 +8880,7 @@ merge@^2.1.1: resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -8620,11 +9173,28 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.3: +object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + on-finished@^2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" @@ -8695,6 +9265,15 @@ ora@^7.0.1: string-width "^6.1.0" strip-ansi "^7.1.0" +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -8949,6 +9528,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + postcss-load-config@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz" @@ -8975,6 +9559,15 @@ prettier@^3.3.3: resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +pretty-format@30.0.5, pretty-format@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + pretty-format@^24: version "24.9.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz" @@ -9202,7 +9795,7 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -9251,6 +9844,20 @@ readline@^1.3.0: resolved "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz" integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz" @@ -9280,6 +9887,30 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" +regexp.escape@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexp.escape/-/regexp.escape-2.0.1.tgz#09e4beef9d202dbd739868f3818223f977cf91da" + integrity sha512-JItRb4rmyTzmERBkAf6J87LjDPy/RscIwmaJQ3gsFlAzrmZbZU8LwBw5IydFZXW9hqpgbPlGbMhtpqtuAhMgtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + for-each "^0.3.3" + safe-regex-test "^1.0.3" + +regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + regexpu-core@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz" @@ -9457,6 +10088,17 @@ rxjs@^7.0.0, rxjs@^7.8.1: dependencies: tslib "^2.1.0" +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" + isarray "^2.0.5" + safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" @@ -9467,6 +10109,23 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + safe-stable-stringify@^2.3.1: version "2.5.0" resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" @@ -9576,6 +10235,37 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" @@ -9759,6 +10449,13 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" @@ -9771,6 +10468,14 @@ stdin-discarder@^0.1.0: dependencies: bl "^5.0.0" +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + streamx@^2.15.0, streamx@^2.21.0: version "2.22.0" resolved "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz" @@ -9825,6 +10530,38 @@ string-width@^6.1.0: emoji-regex "^10.2.1" strip-ansi "^7.0.1" +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + +string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -10232,6 +10969,51 @@ type-is@^2.0.0, type-is@^2.0.1: media-typer "^1.1.0" mime-types "^3.0.0" +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + typed-query-selector@^2.12.0: version "2.12.0" resolved "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz" @@ -10251,6 +11033,16 @@ typescript@^5.4.3: resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" @@ -10460,11 +11252,64 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which@^1.2.10, which@^1.2.14: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" From 75235cafeb156b722989ab8d3ff3035afecdb5b8 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 8 Aug 2025 12:48:42 -0400 Subject: [PATCH 422/422] [compiler] Standarize CompilerDiagnostic descriptions (trailing periods) Separated this out into its own PR since it's really a cosmetic change. CompilerErrorDetail and CompilerDiagnostic now both expect `reason`/`category` and `description` to *not* have trailing periods. --- .../src/CompilerError.ts | 17 ++++++++------ .../ValidateNoUntransformedReferences.ts | 4 +--- .../Inference/InferMutationAliasingEffects.ts | 4 ++-- .../src/Utils/CompilerErrorCodes.ts | 22 ++++++++++--------- .../ValidatePreservedManualMemoization.ts | 4 ++-- ...global-in-component-tag-function.expect.md | 2 +- ...or.assign-global-in-jsx-children.expect.md | 2 +- ...rror.bailout-on-flow-suppression.expect.md | 2 +- ...ut-on-suppression-of-custom-rule.expect.md | 4 ++-- .../error.capture-ref-for-mutation.expect.md | 4 ++-- .../compiler/error.hook-ref-value.expect.md | 4 ++-- ...invalid-access-ref-during-render.expect.md | 2 +- ...valid-access-ref-in-reducer-init.expect.md | 2 +- ...or.invalid-access-ref-in-reducer.expect.md | 2 +- ...-mutate-object-with-ref-function.expect.md | 2 +- ...-access-ref-in-state-initializer.expect.md | 2 +- ...-callback-invoked-during-render-.expect.md | 2 +- ...rrent-inferred-ref-during-render.expect.md | 2 +- ...-conditional-setState-in-useMemo.expect.md | 4 ++-- ...destructure-assignment-to-global.expect.md | 2 +- ...ucture-to-local-global-variables.expect.md | 2 +- ...-disallow-mutating-ref-in-render.expect.md | 2 +- ...tating-refs-in-render-transitive.expect.md | 2 +- ...lid-global-reassignment-indirect.expect.md | 2 +- .../error.invalid-hoisting-setstate.expect.md | 2 +- ...valid-mutate-context-in-callback.expect.md | 2 +- .../error.invalid-mutate-context.expect.md | 2 +- ...ror.invalid-pass-ref-to-function.expect.md | 2 +- ...d-ref-prop-in-render-destructure.expect.md | 2 +- ...ref-prop-in-render-property-load.expect.md | 2 +- ...local-variable-in-async-callback.expect.md | 2 +- ...n-callback-invoked-during-render.expect.md | 2 +- ...error.invalid-ref-value-as-props.expect.md | 2 +- ...d-set-and-read-ref-during-render.expect.md | 4 ++-- ...ef-nested-property-during-render.expect.md | 4 ++-- ...-in-useMemo-indirect-useCallback.expect.md | 2 +- ...rror.invalid-setState-in-useMemo.expect.md | 4 ++-- ....invalid-sketchy-code-use-forget.expect.md | 4 ++-- ...alid-unclosed-eslint-suppression.expect.md | 2 +- ...nconditional-set-state-in-render.expect.md | 4 ++-- ...f-added-to-dep-without-type-info.expect.md | 4 ++-- ...rite-but-dont-read-ref-in-render.expect.md | 2 +- ...invalid-write-ref-prop-in-render.expect.md | 2 +- .../error.reassign-global-fn-arg.expect.md | 2 +- ....reassignment-to-global-indirect.expect.md | 4 ++-- .../error.reassignment-to-global.expect.md | 4 ++-- ...ror.ref-initialization-arbitrary.expect.md | 4 ++-- .../error.ref-initialization-call-2.expect.md | 2 +- .../error.ref-initialization-call.expect.md | 2 +- .../error.ref-initialization-linear.expect.md | 2 +- .../error.ref-initialization-nonif.expect.md | 4 ++-- .../error.ref-initialization-other.expect.md | 2 +- ...ref-initialization-post-access-2.expect.md | 2 +- ...r.ref-initialization-post-access.expect.md | 2 +- .../compiler/error.ref-optional.expect.md | 2 +- ...ror.sketchy-code-exhaustive-deps.expect.md | 2 +- ...rror.sketchy-code-rules-of-hooks.expect.md | 2 +- ...ified-later-preserve-memoization.expect.md | 2 +- ...state-in-render-after-loop-break.expect.md | 2 +- ...l-set-state-in-render-after-loop.expect.md | 2 +- ...-state-in-render-with-loop-throw.expect.md | 2 +- ...r.unconditional-set-state-lambda.expect.md | 2 +- ...tate-nested-function-expressions.expect.md | 2 +- ...ror.update-global-should-bailout.expect.md | 2 +- ...ia-function-preserve-memoization.expect.md | 2 +- ...operty-dont-preserve-memoization.expect.md | 2 +- .../error.useMemo-no-return-value.expect.md | 4 ++-- ...alidate-mutate-ref-arg-in-render.expect.md | 2 +- .../fbt/error.todo-fbt-as-local.expect.md | 2 +- ...rror.todo-fbt-unknown-enum-value.expect.md | 2 +- .../error.todo-locally-require-fbt.expect.md | 2 +- .../error.todo-multiple-fbt-plural.expect.md | 2 +- .../dynamic-gating-invalid-multiple.expect.md | 2 +- .../bailout-retry/error.todo-syntax.expect.md | 2 +- ...utate-after-useeffect-ref-access.expect.md | 2 +- ...in-catch-in-outer-try-with-catch.expect.md | 2 +- .../invalid-jsx-in-try-with-catch.expect.md | 2 +- ...setState-in-useEffect-transitive.expect.md | 2 +- .../invalid-setState-in-useEffect.expect.md | 2 +- ...rozen-hoisted-storecontext-const.expect.md | 2 +- ....reassignment-to-global-indirect.expect.md | 4 ++-- .../error.reassignment-to-global.expect.md | 4 ++-- ...utate-after-useeffect-ref-access.expect.md | 2 +- ....maybe-mutable-ref-not-preserved.expect.md | 2 +- .../error.useMemo-with-refs.flow.expect.md | 2 +- ...-constructed-component-in-render.expect.md | 2 +- ...ly-construct-component-in-render.expect.md | 2 +- ...y-constructed-component-function.expect.md | 2 +- ...onstructed-component-method-call.expect.md | 2 +- ...ically-constructed-component-new.expect.md | 2 +- .../bailout-retry/error.todo-syntax.expect.md | 2 +- ...ror.untransformed-fire-reference.expect.md | 2 +- .../bailout-retry/error.use-no-memo.expect.md | 2 +- 93 files changed, 132 insertions(+), 129 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index 6bce5dca2a..42f1e7c319 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -129,7 +129,7 @@ export class CompilerDiagnostic { const errorEntry = ErrorCodeDetails[code]; let description = undefined; if (errorEntry.description != null) { - description = errorEntry.description; + description = errorEntry.description + '.'; } if (options?.description != null && options.description.length > 0) { if (description != null && description.length > 0) { @@ -137,7 +137,7 @@ export class CompilerDiagnostic { } else { description = ''; } - description += options.description; + description += options.description + '.'; } const diagnosticOptions: CompilerDiagnosticOptions = { @@ -284,22 +284,25 @@ export class CompilerErrorDetail { if (this.options.errorCode != null) { let description = undefined; if (ErrorCodeDetails[this.options.errorCode].description != null) { - description = ErrorCodeDetails[this.options.errorCode].description; + description = + ErrorCodeDetails[this.options.errorCode].description + '.'; } if ( this.options.description != null && this.options.description.length > 0 ) { if (description != null && description.length > 0) { - description += '. '; + description += ' '; } else { description = ''; } - description += this.options.description; + description += this.options.description + '.'; } return description; } - return this.options.description; + return this.options.description + ? this.options.description + '.' + : this.options.description; } get severity(): ErrorSeverity { if (this.options.errorCode != null) { @@ -327,7 +330,7 @@ export class CompilerErrorDetail { printErrorMessage(source: string, options: PrintErrorMessageOptions): string { const buffer = [printErrorSummary(this.severity, this.reason)]; if (this.description != null) { - buffer.push(`\n\n${this.description}.`); + buffer.push(`\n\n${this.description}`); } const loc = this.loc; if (loc != null && typeof loc !== 'symbol') { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 5271d66886..19c699fd7f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -89,9 +89,7 @@ function assertValidEffectImportReference( */ logAndThrowDiagnostic( CompilerDiagnostic.fromCode(ErrorCode.DID_NOT_INFER_DEPS, { - description: - 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' + - (maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''), + description: maybeErrorDiagnostic ? `${maybeErrorDiagnostic}` : '', details: [ { kind: 'error', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index 60146d8335..9dc5b0a2ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -457,7 +457,7 @@ function applySignature( place: effect.value, // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { - description: ErrorCodeDetails[errorCode].reason + '.', + description: ErrorCodeDetails[errorCode].reason, }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -1074,7 +1074,7 @@ function applyEffect( place: effect.value, // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { - description: ErrorCodeDetails[errorCode].reason + '.', + description: ErrorCodeDetails[errorCode].reason, }).withDetail({ kind: 'error', loc: effect.value.loc, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts index 51eff972ca..a646368cc0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts @@ -147,7 +147,7 @@ export const ErrorCodeDetails: Record = { severity: ErrorSeverity.InvalidReact, reason: 'Cannot call impure functions during render', description: - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).', + 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', linterCategory: LinterCategory.IMPURE_FUNCTIONS, }, [ErrorCode.JSX_IN_TRY]: { @@ -186,14 +186,14 @@ export const ErrorCodeDetails: Record = { code: ErrorCode.WRITE_AFTER_RENDER, severity: ErrorSeverity.InvalidReact, reason: 'Cannot modify local variables after render completes', - description: `This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`, + description: `This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead`, linterCategory: LinterCategory.INVALID_WRITE, }, [ErrorCode.REASSIGN_AFTER_RENDER]: { code: ErrorCode.REASSIGN_AFTER_RENDER, severity: ErrorSeverity.InvalidReact, reason: 'Cannot reassign local variables after render completes', - description: `Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`, + description: `Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead`, linterCategory: LinterCategory.INVALID_WRITE, }, [ErrorCode.REASSIGN_IN_ASYNC]: { @@ -253,7 +253,7 @@ export const ErrorCodeDetails: Record = { severity: ErrorSeverity.InvalidReact, reason: 'useMemo() callbacks may not accept parameters', description: - 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', + 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation', linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, }, [ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC]: { @@ -261,7 +261,7 @@ export const ErrorCodeDetails: Record = { severity: ErrorSeverity.InvalidReact, reason: 'useMemo() callbacks may not be async or generator functions', description: - 'useMemo() callbacks are called once and must synchronously return a value.', + 'useMemo() callbacks are called once and must synchronously return a value', linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, }, [ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN]: { @@ -331,7 +331,7 @@ export const ErrorCodeDetails: Record = { [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT]: { code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, severity: ErrorSeverity.InvalidReact, - reason: `Modifying a value returned from 'useContext()' is not allowed.`, + reason: `Modifying a value returned from 'useContext()' is not allowed`, linterCategory: LinterCategory.INVALID_WRITE, }, [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE]: { @@ -413,7 +413,7 @@ export const ErrorCodeDetails: Record = { [ErrorCode.FILENAME_NOT_SET]: { code: ErrorCode.FILENAME_NOT_SET, severity: ErrorSeverity.InvalidConfig, - reason: `Expected a filename but found none.`, + reason: `Expected a filename but found none`, description: "When the 'sources' config options is specified, the React compiler will only compile files with a name", linterCategory: LinterCategory.COMPILER_CONFIG, @@ -545,7 +545,7 @@ export const ErrorCodeDetails: Record = { reason: 'Compilation skipped because existing memoization could not be preserved', description: - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.', + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output', severity: ErrorSeverity.CannotPreserveMemoization, linterCategory: LinterCategory.TODO_SYNTAX, }, @@ -563,6 +563,8 @@ export const ErrorCodeDetails: Record = { code: ErrorCode.DID_NOT_INFER_DEPS, reason: 'Cannot infer dependencies of this effect. This will break your build!', + description: + 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics', severity: ErrorSeverity.InvalidReact, linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, }, @@ -604,7 +606,7 @@ export const ErrorCodeDetails: Record = { 'Compilation skipped because existing memoization could not be preserved', description: [ 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', - 'This dependency may be mutated later, which could cause the value to change unexpectedly.', + 'This dependency may be mutated later, which could cause the value to change unexpectedly', ].join(''), severity: ErrorSeverity.CannotPreserveMemoization, linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, @@ -615,7 +617,7 @@ export const ErrorCodeDetails: Record = { 'Compilation skipped because existing memoization could not be preserved', description: '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.', + 'The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected', severity: ErrorSeverity.CannotPreserveMemoization, linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, }, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index 7776e15cbb..43dc98668f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -289,7 +289,7 @@ function validateInferredDep( errorDiagnostic ? getCompareDependencyResultDescription(errorDiagnostic) : 'Inferred dependency not present in source' - }.` + }` : '', suggestions: null, }).withDetail({ @@ -566,7 +566,7 @@ class Visitor extends ReactiveFunctionVisitor { state.errors.pushDiagnostic( CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_REMOVED, { description: DEBUG - ? `${printIdentifier(identifier)} was not memoized.` + ? `${printIdentifier(identifier)} was not memoized` : '', }).withDetail({ kind: 'error', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md index e47107723f..b24da3d8c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.assign-global-in-component-tag-function.ts:3:4 1 | function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md index 5acfcde730..20349d9050 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md @@ -22,7 +22,7 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.assign-global-in-jsx-children.ts:3:4 1 | function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md index a5530a1180..fe0ade4b91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md @@ -20,7 +20,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `$FlowFixMe[react-rule-hook]` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `$FlowFixMe[react-rule-hook]`. error.bailout-on-flow-suppression.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index b348bc6191..ed9f73a016 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -23,7 +23,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable my-app/react-rule`. error.bailout-on-suppression-of-custom-rule.ts:3:0 1 | // @eslintSuppressionRules:["my-app","react-rule"] @@ -36,7 +36,7 @@ error.bailout-on-suppression-of-custom-rule.ts:3:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line my-app/react-rule` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line my-app/react-rule`. error.bailout-on-suppression-of-custom-rule.ts:7:2 5 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md index cb2256a187..a3b2ace512 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md @@ -36,7 +36,7 @@ Found 2 errors: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.capture-ref-for-mutation.ts:12:13 10 | }; @@ -49,7 +49,7 @@ error.capture-ref-for-mutation.ts:12:13 Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.capture-ref-for-mutation.ts:15:13 13 | }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md index 36949c6504..cf9a6a5b4c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md @@ -24,7 +24,7 @@ Found 2 errors: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.hook-ref-value.ts:5:23 3 | function Component(props) { @@ -37,7 +37,7 @@ error.hook-ref-value.ts:5:23 Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.hook-ref-value.ts:5:23 3 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md index 989e68efd8..94a9a984c2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-access-ref-during-render.ts:4:16 2 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.expect.md index 29fe24a220..647cf28f7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.expect.md @@ -30,7 +30,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-access-ref-in-reducer-init.ts:8:4 6 | (state, action) => state + action, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.expect.md index f23560b4f6..33fcd6d188 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.expect.md @@ -26,7 +26,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-access-ref-in-reducer.ts:5:29 3 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-render-mutate-object-with-ref-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-render-mutate-object-with-ref-function.expect.md index a70fcf39b3..73cead6aff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-render-mutate-object-with-ref-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-render-mutate-object-with-ref-function.expect.md @@ -22,7 +22,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-access-ref-in-render-mutate-object-with-ref-function.ts:7:19 5 | const object = {}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.expect.md index dd6a64d9db..a10db96463 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.expect.md @@ -26,7 +26,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-access-ref-in-state-initializer.ts:5:27 3 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md index 3aa5237533..09a64d4bab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md @@ -23,7 +23,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:9:33 7 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-assign-current-inferred-ref-during-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-assign-current-inferred-ref-during-render.expect.md index 4f4ed63550..293596a540 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-assign-current-inferred-ref-during-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-assign-current-inferred-ref-during-render.expect.md @@ -22,7 +22,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 4 | component Example() { 5 | const fooRef = makeObject_Primitives(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md index c99dfc1e19..e4a9424962 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md @@ -26,7 +26,7 @@ Found 2 errors: Error: Calling setState from useMemo may trigger an infinite loop -Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState) +Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState). error.invalid-conditional-setState-in-useMemo.ts:7:6 5 | useMemo(() => { @@ -39,7 +39,7 @@ error.invalid-conditional-setState-in-useMemo.ts:7:6 Error: Calling setState from useMemo may trigger an infinite loop -Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState) +Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState). error.invalid-conditional-setState-in-useMemo.ts:8:6 6 | if (cond) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md index 96485fb584..a70e28133b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md @@ -17,7 +17,7 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.invalid-destructure-assignment-to-global.ts:2:3 1 | function useFoo(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md index bae0df94af..9912a440bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.invalid-destructure-to-local-global-variables.ts:3:6 1 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-ref-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-ref-in-render.expect.md index 9f19d10b9d..c43bb5dcd9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-ref-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-ref-in-render.expect.md @@ -20,7 +20,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-disallow-mutating-ref-in-render.ts:4:2 2 | function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-refs-in-render-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-refs-in-render-transitive.expect.md index 740a0519d5..c8b70edcc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-refs-in-render-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-refs-in-render-transitive.expect.md @@ -25,7 +25,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-disallow-mutating-refs-in-render-transitive.ts:9:2 7 | }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md index f3f2cff6e7..426f6dba18 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md @@ -39,7 +39,7 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.invalid-global-reassignment-indirect.ts:9:4 7 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md index 8c512ae350..5be7ca4073 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md @@ -42,7 +42,7 @@ Found 1 error: Error: Cannot access variable before it is declared -Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized. error.invalid-hoisting-setstate.ts:19:18 17 | * $2 = Function context=setState diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context-in-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context-in-callback.expect.md index 9467aa21e0..0d06185441 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context-in-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context-in-callback.expect.md @@ -28,7 +28,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a value returned from 'useContext()' is not allowed.. +Modifying a value returned from 'useContext()' is not allowed. error.invalid-mutate-context-in-callback.ts:12:4 10 | // independently diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context.expect.md index eddbfb5eac..aa04d4d543 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context.expect.md @@ -18,7 +18,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a value returned from 'useContext()' is not allowed.. +Modifying a value returned from 'useContext()' is not allowed. error.invalid-mutate-context.ts:3:2 1 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.expect.md index 79c2a2e4f6..eaa140eb90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-pass-ref-to-function.ts:4:16 2 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md index 5521300e29..cca903de74 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md @@ -18,7 +18,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-read-ref-prop-in-render-destructure.ts:3:16 1 | // @validateRefAccessDuringRender @compilationMode:"infer" diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md index 11d95823d4..49b8e5d199 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md @@ -18,7 +18,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-read-ref-prop-in-render-property-load.ts:3:16 1 | // @validateRefAccessDuringRender @compilationMode:"infer" diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-async-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-async-callback.expect.md index aa9c0bffe4..4e397afd6a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-async-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-async-callback.expect.md @@ -29,7 +29,7 @@ Found 1 error: Error: Cannot reassign variable in async function -Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead +Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead. error.invalid-reassign-local-variable-in-async-callback.ts:8:6 6 | // after render, so this should error regardless of where this ends up diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-in-callback-invoked-during-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-in-callback-invoked-during-render.expect.md index 414ee9d536..df1e771fa2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-in-callback-invoked-during-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-in-callback-invoked-during-render.expect.md @@ -22,7 +22,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-ref-in-callback-invoked-during-render.ts:8:33 6 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-value-as-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-value-as-props.expect.md index 2bbde91d8f..d581232b3a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-value-as-props.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-value-as-props.expect.md @@ -18,7 +18,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-ref-value-as-props.ts:4:19 2 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-during-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-during-render.expect.md index 296b9f0831..387dff27bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-during-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-during-render.expect.md @@ -19,7 +19,7 @@ Found 2 errors: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-set-and-read-ref-during-render.ts:4:2 2 | function Component(props) { @@ -32,7 +32,7 @@ error.invalid-set-and-read-ref-during-render.ts:4:2 Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-set-and-read-ref-during-render.ts:5:9 3 | const ref = useRef(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-nested-property-during-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-nested-property-during-render.expect.md index ff57f3d171..8ef0e223a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-nested-property-during-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-nested-property-during-render.expect.md @@ -19,7 +19,7 @@ Found 2 errors: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-set-and-read-ref-nested-property-during-render.ts:4:2 2 | function Component(props) { @@ -32,7 +32,7 @@ error.invalid-set-and-read-ref-nested-property-during-render.ts:4:2 Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-set-and-read-ref-nested-property-during-render.ts:5:9 3 | const ref = useRef({inner: null}); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md index 48188548fc..e284a9367f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md @@ -30,7 +30,7 @@ Found 1 error: Error: Calling setState from useMemo may trigger an infinite loop -Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState) +Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState). error.invalid-setState-in-useMemo-indirect-useCallback.ts:13:4 11 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo.expect.md index 16a9386f4d..04d82e429f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo.expect.md @@ -24,7 +24,7 @@ Found 2 errors: Error: Calling setState from useMemo may trigger an infinite loop -Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState) +Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState). error.invalid-setState-in-useMemo.ts:6:4 4 | @@ -37,7 +37,7 @@ error.invalid-setState-in-useMemo.ts:6:4 Error: Calling setState from useMemo may trigger an infinite loop -Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState) +Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState). error.invalid-setState-in-useMemo.ts:7:4 5 | useMemo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md index 14d6068b97..be22558e3c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`. error.invalid-sketchy-code-use-forget.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ @@ -32,7 +32,7 @@ error.invalid-sketchy-code-use-forget.ts:1:0 Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks`. error.invalid-sketchy-code-use-forget.ts:5:2 3 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md index d77327b90d..9b7883f617 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md @@ -40,7 +40,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`. error.invalid-unclosed-eslint-suppression.ts:2:0 1 | // Note: Everything below this is sketchy diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md index f10764dc70..478484770f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md @@ -23,7 +23,7 @@ Found 2 errors: Error: Calling setState during render may trigger an infinite loop -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState) +Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState). error.invalid-unconditional-set-state-in-render.ts:6:2 4 | const aliased = setX; @@ -36,7 +36,7 @@ error.invalid-unconditional-set-state-in-render.ts:6:2 Error: Calling setState during render may trigger an infinite loop -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState) +Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState). error.invalid-unconditional-set-state-in-render.ts:7:2 5 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-use-ref-added-to-dep-without-type-info.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-use-ref-added-to-dep-without-type-info.expect.md index f41ae64ce7..53bf66b1ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-use-ref-added-to-dep-without-type-info.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-use-ref-added-to-dep-without-type-info.expect.md @@ -26,7 +26,7 @@ Found 2 errors: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-use-ref-added-to-dep-without-type-info.ts:10:21 8 | // however, this is an instance of accessing a ref during render and is disallowed @@ -39,7 +39,7 @@ error.invalid-use-ref-added-to-dep-without-type-info.ts:10:21 Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-use-ref-added-to-dep-without-type-info.ts:12:28 10 | const x = {a, val: val.ref.current}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-but-dont-read-ref-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-but-dont-read-ref-in-render.expect.md index abce1ed344..94223bb898 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-but-dont-read-ref-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-but-dont-read-ref-in-render.expect.md @@ -21,7 +21,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-write-but-dont-read-ref-in-render.ts:5:2 3 | const ref = useRef(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md index 0e76607498..f8d4492609 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.invalid-write-ref-prop-in-render.ts:4:2 2 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md index cd7f202a1c..223fae641c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md @@ -28,7 +28,7 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.reassign-global-fn-arg.ts:5:4 3 | export default function MyApp() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md index 6963940109..e9cef55f66 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.reassignment-to-global-indirect.ts:4:4 2 | const foo = () => { @@ -34,7 +34,7 @@ error.reassignment-to-global-indirect.ts:4:4 Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.reassignment-to-global-indirect.ts:5:4 3 | // Cannot assign to globals diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md index 88ae3a7ae8..a45a3f4ac1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md @@ -18,7 +18,7 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.reassignment-to-global.ts:3:2 1 | function Component() { @@ -31,7 +31,7 @@ error.reassignment-to-global.ts:3:2 Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.reassignment-to-global.ts:4:2 2 | // Cannot assign to globals diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-arbitrary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-arbitrary.expect.md index 2a72559281..17625298cd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-arbitrary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-arbitrary.expect.md @@ -29,7 +29,7 @@ Found 2 errors: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 6 | component C() { 7 | const r = useRef(DEFAULT_VALUE); @@ -41,7 +41,7 @@ React refs are values that are not needed for rendering. Refs should only be acc Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 7 | const r = useRef(DEFAULT_VALUE); 8 | if (r.current == DEFAULT_VALUE) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call-2.expect.md index f3b292f658..3a6d0b0f7a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call-2.expect.md @@ -27,7 +27,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 5 | const r = useRef(null); 6 | if (r.current == null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call.expect.md index d57c3ee010..d9f3ac3aad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-call.expect.md @@ -27,7 +27,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 5 | const r = useRef(null); 6 | if (r.current == null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-linear.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-linear.expect.md index 211dee52c8..7c6e6d5d58 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-linear.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-linear.expect.md @@ -28,7 +28,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 6 | if (r.current == null) { 7 | r.current = 42; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-nonif.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-nonif.expect.md index 6388f01ee2..aaa86f9141 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-nonif.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-nonif.expect.md @@ -28,7 +28,7 @@ Found 2 errors: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 4 | component C() { 5 | const r = useRef(null); @@ -40,7 +40,7 @@ React refs are values that are not needed for rendering. Refs should only be acc Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 5 | const r = useRef(null); 6 | const guard = r.current == null; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-other.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-other.expect.md index 4103eaa291..e1bda18890 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-other.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-other.expect.md @@ -28,7 +28,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 6 | const r2 = useRef(null); 7 | if (r.current == null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access-2.expect.md index f04df650ad..e30d6103a2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access-2.expect.md @@ -28,7 +28,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 7 | r.current = 1; 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access.expect.md index b432538f61..f8d08dda47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-initialization-post-access.expect.md @@ -28,7 +28,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 7 | r.current = 1; 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-optional.expect.md index 80609e0338..94e4b0fc46 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-optional.expect.md @@ -24,7 +24,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.ref-optional.ts:5:9 3 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md index 0bcfae2e9b..92c0d5ab1a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md @@ -24,7 +24,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps`. error.sketchy-code-exhaustive-deps.ts:6:7 4 | () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md index bd1b3ed4c7..2e95c63789 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md @@ -25,7 +25,7 @@ Found 1 error: Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior Found suppression `eslint-disable react-hooks/rules-of-hooks` +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`. error.sketchy-code-rules-of-hooks.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-useCallback-set-ref-nested-property-ref-modified-later-preserve-memoization.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-useCallback-set-ref-nested-property-ref-modified-later-preserve-memoization.expect.md index fb472f683b..a0c492120a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-useCallback-set-ref-nested-property-ref-modified-later-preserve-memoization.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-useCallback-set-ref-nested-property-ref-modified-later-preserve-memoization.expect.md @@ -35,7 +35,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.todo-useCallback-set-ref-nested-property-ref-modified-later-preserve-memoization.ts:14:2 12 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md index 6f9da8fda9..2e90cc045e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md @@ -26,7 +26,7 @@ Found 1 error: Error: Calling setState during render may trigger an infinite loop -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState) +Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState). error.unconditional-set-state-in-render-after-loop-break.ts:11:2 9 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md index 6ab4ecb36e..9ce285b3b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md @@ -21,7 +21,7 @@ Found 1 error: Error: Calling setState during render may trigger an infinite loop -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState) +Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState). error.unconditional-set-state-in-render-after-loop.ts:6:2 4 | for (const _ of props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md index 69c81c3ff8..265e31fdfd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md @@ -26,7 +26,7 @@ Found 1 error: Error: Calling setState during render may trigger an infinite loop -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState) +Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState). error.unconditional-set-state-in-render-with-loop-throw.ts:11:2 9 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md index 79369f3608..14bb899a06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md @@ -24,7 +24,7 @@ Found 1 error: Error: Calling setState during render may trigger an infinite loop -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState) +Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState). error.unconditional-set-state-lambda.ts:8:2 6 | setX(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md index c8a775499a..7c231f5740 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md @@ -32,7 +32,7 @@ Found 1 error: Error: Calling setState during render may trigger an infinite loop -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState) +Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState). error.unconditional-set-state-nested-function-expressions.ts:16:2 14 | bar(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md index cfd27c7356..fb07ccc97e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md @@ -23,7 +23,7 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.update-global-should-bailout.ts:3:2 1 | let renderCount = 0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useCallback-accesses-ref-mutated-later-via-function-preserve-memoization.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useCallback-accesses-ref-mutated-later-via-function-preserve-memoization.expect.md index f93e987565..ba5a740777 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useCallback-accesses-ref-mutated-later-via-function-preserve-memoization.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useCallback-accesses-ref-mutated-later-via-function-preserve-memoization.expect.md @@ -38,7 +38,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.useCallback-accesses-ref-mutated-later-via-function-preserve-memoization.ts:17:2 15 | ref.current.inner = null; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useCallback-set-ref-nested-property-dont-preserve-memoization.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useCallback-set-ref-nested-property-dont-preserve-memoization.expect.md index 74822389a5..b40b0bbf22 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useCallback-set-ref-nested-property-dont-preserve-memoization.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useCallback-set-ref-nested-property-dont-preserve-memoization.expect.md @@ -34,7 +34,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.useCallback-set-ref-nested-property-dont-preserve-memoization.ts:13:2 11 | }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-no-return-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-no-return-value.expect.md index fb59596406..0f5354b14d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-no-return-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-no-return-value.expect.md @@ -28,7 +28,7 @@ Found 2 errors: Error: useMemo() callbacks must return a value -This useMemo callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects. +This useMemo callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.. error.useMemo-no-return-value.ts:3:16 1 | // @validateNoVoidUseMemo @@ -45,7 +45,7 @@ error.useMemo-no-return-value.ts:3:16 Error: useMemo() callbacks must return a value -This React.useMemo callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects. +This React.useMemo callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.. error.useMemo-no-return-value.ts:6:17 4 | console.log('computing'); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-mutate-ref-arg-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-mutate-ref-arg-in-render.expect.md index 06378fe0d0..1d5a4a2284 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-mutate-ref-arg-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-mutate-ref-arg-in-render.expect.md @@ -24,7 +24,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.validate-mutate-ref-arg-in-render.ts:3:14 1 | // @validateRefAccessDuringRender:true diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-as-local.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-as-local.expect.md index 2e75e45782..bb86d3bc42 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-as-local.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-as-local.expect.md @@ -54,7 +54,7 @@ Found 1 error: Todo: Support local variables named `fbt` -Local variables named `fbt` may conflict with the fbt plugin and are not yet supported +Local variables named `fbt` may conflict with the fbt plugin and are not yet supported. error.todo-fbt-as-local.ts:18:19 16 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-unknown-enum-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-unknown-enum-value.expect.md index 6251105d1c..3999d17d51 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-unknown-enum-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-unknown-enum-value.expect.md @@ -23,7 +23,7 @@ Found 1 error: Todo: Support duplicate fbt tags -Support `` tags with multiple `` values +Support `` tags with multiple `` values. error.todo-fbt-unknown-enum-value.ts:6:7 4 | return ( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-locally-require-fbt.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-locally-require-fbt.expect.md index 1dbe86dc3a..62605e5896 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-locally-require-fbt.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-locally-require-fbt.expect.md @@ -18,7 +18,7 @@ Found 1 error: Todo: Support local variables named `fbt` -Local variables named `fbt` may conflict with the fbt plugin and are not yet supported +Local variables named `fbt` may conflict with the fbt plugin and are not yet supported. error.todo-locally-require-fbt.ts:2:8 1 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-multiple-fbt-plural.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-multiple-fbt-plural.expect.md index 71c1ef2c83..275f0ef067 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-multiple-fbt-plural.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-multiple-fbt-plural.expect.md @@ -57,7 +57,7 @@ Found 1 error: Todo: Support duplicate fbt tags -Support `` tags with multiple `` values +Support `` tags with multiple `` values. error.todo-multiple-fbt-plural.ts:29:7 27 | return ( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md index b4f642df43..45cf16c94a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Expected a single dynamic gating directive","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Expected a single dynamic gating directive","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)].","severity":"InvalidReact","loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md index 57ccade29d..da79edabc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md @@ -36,7 +36,7 @@ Found 1 error: Error: Cannot infer dependencies of this effect. This will break your build! -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (13:6) +To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (13:6). error.todo-syntax.ts:11:2 9 | function Component({prop1}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index d3b746b1f8..f7c727ccd4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"severity":"InvalidReact","category":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"severity":"InvalidReact","category":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":237},"end":{"line":8,"column":50,"index":285},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":259},"end":{"line":8,"column":30,"index":265},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md index 6583f0a4e2..4a567226b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md @@ -65,7 +65,7 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","details":[{"kind":"error","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary).","details":[{"kind":"error","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":17,"column":1,"index":298},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md index b6e9e87ada..e06f946934 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md @@ -42,7 +42,7 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","details":[{"kind":"error","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary).","details":[{"kind":"error","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":49},"end":{"line":10,"column":1,"index":160},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index 0925a9b982..9e692936f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -65,7 +65,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).","details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index 3e3135929b..cf99e06306 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -45,7 +45,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).","details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md index bef031723c..586af5b2c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md @@ -35,7 +35,7 @@ Found 1 error: Error: Cannot access variable before it is declared -Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized. 9 | // TDZ violation! 10 | const onRefetch = useCallback(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md index bb4d91a4bb..0921cd71f2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md @@ -22,7 +22,7 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.reassignment-to-global-indirect.ts:5:4 3 | const foo = () => { @@ -35,7 +35,7 @@ error.reassignment-to-global-indirect.ts:5:4 Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.reassignment-to-global-indirect.ts:6:4 4 | // Cannot assign to globals diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md index 84339b0759..c1e3cef563 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md @@ -19,7 +19,7 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.reassignment-to-global.ts:4:2 2 | function Component() { @@ -32,7 +32,7 @@ error.reassignment-to-global.ts:4:2 Error: Cannot reassign variables declared outside of the component/hook -Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.reassignment-to-global.ts:5:2 3 | // Cannot assign to globals diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md index 7bcc917447..675d8c4f92 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"severity":"InvalidReact","category":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"severity":"InvalidReact","category":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":269},"end":{"line":8,"column":50,"index":317},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":291},"end":{"line":8,"column":30,"index":297},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-mutable-ref-not-preserved.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-mutable-ref-not-preserved.expect.md index 82bce33951..77f104cab0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-mutable-ref-not-preserved.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.maybe-mutable-ref-not-preserved.expect.md @@ -27,7 +27,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). error.maybe-mutable-ref-not-preserved.ts:8:33 6 | function useFoo() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-with-refs.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-with-refs.flow.expect.md index 19ffca8af6..0269b22a1f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-with-refs.flow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-with-refs.flow.expect.md @@ -23,7 +23,7 @@ Found 1 error: Error: Cannot access refs during render -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef) +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). 5 | const localRef = useFooRef(); 6 | const mergedRef = useMemo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md index 1224a5b9cf..58f922b5df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md @@ -50,7 +50,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. .","details":[{"kind":"error","loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":10,"column":1,"index":217},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md index 7bec7f73b5..88ad52929a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md @@ -32,7 +32,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. .","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":135},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md index e7f9ad7f30..aa9648d0ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md @@ -37,7 +37,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. .","details":[{"kind":"error","loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":7,"column":1,"index":145},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md index f6220645ef..7bd1720f25 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md @@ -41,7 +41,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. .","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":133},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md index 4a72cdd855..9918aad68e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md @@ -32,7 +32,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. .","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":140},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index bda9c3b694..fdcdd58f8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -33,7 +33,7 @@ Found 1 error: Error: Cannot compile `fire` - Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:4) + Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:4). error.todo-syntax.ts:18:4 16 | }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index 9604f69476..db108275cd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -17,7 +17,7 @@ Found 1 error: Error: Cannot compile `fire` - null + null. error.untransformed-fire-reference.ts:4:12 2 | import {fire} from 'react'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index 02753ce345..22873626d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -34,7 +34,7 @@ Found 1 error: Error: Cannot compile `fire` - null + null. error.use-no-memo.ts:15:4 13 | };