From 8ec32bb27037ef790a46e9fa70ae70b151f18e29 Mon Sep 17 00:00:00 2001 From: Mike Vitousek Date: Mon, 1 Jul 2024 18:02:29 -0700 Subject: [PATCH] [compiler] Add wrapper functions to wrap change-detection storage and loading from the memo cache Summary: We may wish to perform some additional computation on values when they enter or exit the memo cache in change detection mode (e.g. make a deep copy, restore the original value). This builds support for doing so. In addition, it drops the "ForDebugging" part of the flag name and makes it compatible with "disableMemoization": if memoization is disabled, we implement that by not restoring the old version of the value unless we're in a source-level memo block. [ghstack-poisoned] --- .../src/Entrypoint/Pipeline.ts | 4 +- .../src/Entrypoint/Program.ts | 26 +++- .../src/HIR/Environment.ts | 39 ++++-- .../src/Inference/DropManualMemoization.ts | 2 +- .../ReactiveScopes/CodegenReactiveFunction.ts | 77 +++++++---- .../InferReactiveScopeVariables.ts | 2 +- .../ReactiveScopes/PruneNonEscapingScopes.ts | 2 +- .../compiler/change-detect-reassign.expect.md | 5 +- .../compiler/change-detect-reassign.js | 2 +- .../compiler/change-detect-wrapper.expect.md | 51 +++++++ .../compiler/change-detect-wrapper.js | 9 ++ .../error.nomemo-and-change-detect.expect.md | 17 --- .../error.nomemo-and-change-detect.js | 2 - .../nomemo-and-change-detect.expect.md | 128 ++++++++++++++++++ .../compiler/nomemo-and-change-detect.js | 19 +++ ...d-other-hook-unpruned-dependency.expect.md | 8 +- ...tate-and-other-hook-unpruned-dependency.js | 2 +- ...-pruned-dependency-change-detect.expect.md | 6 +- ...seState-pruned-dependency-change-detect.js | 2 +- .../useState-unpruned-dependency.expect.md | 8 +- .../compiler/useState-unpruned-dependency.js | 2 +- compiler/packages/snap/src/compiler.ts | 20 ++- 22 files changed, 343 insertions(+), 90 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-wrapper.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-wrapper.js delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nomemo-and-change-detect.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nomemo-and-change-detect.js 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 a05061f9171..5fdbd77675b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -269,7 +269,7 @@ function* runWithEnvironment( if ( env.config.enablePreserveExistingManualUseMemo === "scope" || - env.config.enableChangeDetectionForDebugging != null || + env.config.enableChangeDetection != null || env.config.disableMemoizationForDebugging ) { memoizeExistingUseMemos(hir); @@ -418,7 +418,7 @@ function* runWithEnvironment( value: reactiveFunction, }); - if (env.config.enableChangeDetectionForDebugging != null) { + if (env.config.enableChangeDetection != null) { pruneInitializationDependencies(reactiveFunction); yield log({ kind: "reactive", 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 adee97238b2..6e6e52720d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -424,11 +424,27 @@ export function compileProgram( externalFunctions.push(enableEmitHookGuards); } - if (pass.opts.environment?.enableChangeDetectionForDebugging != null) { - const enableChangeDetectionForDebugging = tryParseExternalFunction( - pass.opts.environment.enableChangeDetectionForDebugging - ); - externalFunctions.push(enableChangeDetectionForDebugging); + if (pass.opts.environment?.enableChangeDetection != null) { + const enableChangeDetection = tryParseExternalFunction({ + importSpecifierName: + pass.opts.environment.enableChangeDetection.structuralCheck, + source: pass.opts.environment.enableChangeDetection.source, + }); + externalFunctions.push(enableChangeDetection); + if (pass.opts.environment.enableChangeDetection.wrappers != null) { + const store = tryParseExternalFunction({ + importSpecifierName: + pass.opts.environment.enableChangeDetection.wrappers.store, + source: pass.opts.environment.enableChangeDetection.source, + }); + const restore = tryParseExternalFunction({ + importSpecifierName: + pass.opts.environment.enableChangeDetection.wrappers.restore, + source: pass.opts.environment.enableChangeDetection.source, + }); + externalFunctions.push(store); + externalFunctions.push(restore); + } } } catch (err) { handleError(err, pass, 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 0be897344b7..e74fb4a8c1a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -385,7 +385,13 @@ 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(), + enableChangeDetection: z + .object({ + source: z.string(), + structuralCheck: z.string(), + wrappers: z.object({ store: z.string(), restore: z.string() }).nullish(), + }) + .nullish(), /** * The react native re-animated library uses custom Babel transforms that @@ -448,12 +454,27 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { } if ( - key === "enableChangeDetectionForDebugging" && + key === "enableChangeDetection" && (val === undefined || val === "true") ) { maybeConfig[key] = { source: "react-compiler-runtime", - importSpecifierName: "$structuralCheck", + structuralCheck: "$structuralCheck", + }; + continue; + } + + if ( + key === "enableChangeDetectionWrappers" && + (val === undefined || val === "true") + ) { + maybeConfig["enableChangeDetection"] = { + source: "react-compiler-runtime", + structuralCheck: "$structuralCheck", + wrappers: { + store: "$store", + restore: "$restore", + }, }; continue; } @@ -550,18 +571,6 @@ export class Environment { this.#shapes = new Map(DEFAULT_SHAPES); this.#globals = new Map(DEFAULT_GLOBALS); - if ( - config.disableMemoizationForDebugging && - config.enableChangeDetectionForDebugging != null - ) { - CompilerError.throwInvalidConfig({ - reason: `Invalid environment config: the 'disableMemoizationForDebugging' and 'enableChangeDetectionForDebugging' options cannot be used together`, - description: null, - loc: null, - suggestions: null, - }); - } - for (const [hookName, hook] of this.config.customHooks) { CompilerError.invariant(!this.#globals.has(hookName), { reason: `[Globals] Found existing definition in global registry for custom hook ${hookName}`, 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 0798dc717ff..d70b0c0dd18 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -364,7 +364,7 @@ function extractManualMemoizationArgs( export function dropManualMemoization(func: HIRFunction): void { const isManualUseMemoEnabled = func.env.config.enablePreserveExistingManualUseMemo === "scope" || - func.env.config.enableChangeDetectionForDebugging != null || + func.env.config.enableChangeDetection != null || func.env.config.disableMemoizationForDebugging; const isValidationEnabled = func.env.config.validatePreserveExistingMemoizationGuarantees || 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 2fa0dcffa8c..d91284384fc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -622,15 +622,11 @@ function codegenReactiveScope( ); } - if (cx.env.config.disableMemoizationForDebugging && !scope.source) { - CompilerError.invariant( - cx.env.config.enableChangeDetectionForDebugging == null, - { - reason: `Expected to not have both change detection enabled and memoization disabled`, - description: `Incompatible config options`, - loc: null, - } - ); + if ( + cx.env.config.disableMemoizationForDebugging && + !scope.source && + cx.env.config.enableChangeDetection == null + ) { testCondition = t.logicalExpression( "||", testCondition, @@ -641,7 +637,7 @@ function codegenReactiveScope( let memoStatement; if ( - cx.env.config.enableChangeDetectionForDebugging != null && + cx.env.config.enableChangeDetection != null && changeExpressions.length > 0 ) { const loc = @@ -649,44 +645,65 @@ function codegenReactiveScope( ? "unknown location" : `(${scope.loc.start.line}:${scope.loc.end.line})`; const detectionFunction = - cx.env.config.enableChangeDetectionForDebugging.importSpecifierName; + cx.env.config.enableChangeDetection.structuralCheck; const cacheLoadOldValueStatements: Array = []; const changeDetectionStatements: Array = []; const idempotenceDetectionStatements: Array = []; const restoreOldValueStatements: Array = []; - for (const { name, index, value } of cacheLoads) { - const loadName = cx.synthesizeName(`old$${name.name}`); - const slot = t.memberExpression( + for (const { + name: { name: nameStr }, + index, + value, + } of cacheLoads) { + const baseSlot = t.memberExpression( t.identifier(cx.synthesizeName("$")), t.numericLiteral(index), true ); + + const genSlot = (): t.MemberExpression => t.cloneNode(baseSlot, true); + + const loadNameStr = cx.synthesizeName(`old$${nameStr}`); + + let storedValue, restoredValue; + if (cx.env.config.enableChangeDetection.wrappers != null) { + storedValue = t.callExpression( + t.identifier(cx.env.config.enableChangeDetection.wrappers.store), + [value] + ); + restoredValue = t.callExpression( + t.identifier(cx.env.config.enableChangeDetection.wrappers.restore), + [t.identifier(loadNameStr)] + ); + } else { + storedValue = value; + restoredValue = t.identifier(loadNameStr); + } + cacheStoreStatements.push( - t.expressionStatement(t.assignmentExpression("=", slot, value)) + t.expressionStatement( + t.assignmentExpression("=", genSlot(), storedValue) + ) ); cacheLoadOldValueStatements.push( t.variableDeclaration("let", [ - t.variableDeclarator(t.identifier(loadName), slot), + t.variableDeclarator(t.identifier(loadNameStr), genSlot()), ]) ); - if (scope.source) { + if (scope.source || !cx.env.config.disableMemoizationForDebugging) { restoreOldValueStatements.push( t.expressionStatement( - t.assignmentExpression( - "=", - t.cloneNode(name, true), - t.identifier(loadName) - ) + t.assignmentExpression("=", t.identifier(nameStr), restoredValue) ) ); } changeDetectionStatements.push( t.expressionStatement( t.callExpression(t.identifier(detectionFunction), [ - t.identifier(loadName), - t.cloneNode(name, true), - t.stringLiteral(name.name), + t.identifier(loadNameStr), + t.identifier(nameStr), + t.stringLiteral(nameStr), t.stringLiteral(cx.fnName), t.stringLiteral("cached"), t.stringLiteral(loc), @@ -696,9 +713,9 @@ function codegenReactiveScope( idempotenceDetectionStatements.push( t.expressionStatement( t.callExpression(t.identifier(detectionFunction), [ - t.cloneNode(slot, true), - t.cloneNode(name, true), - t.stringLiteral(name.name), + genSlot(), + t.identifier(nameStr), + t.stringLiteral(nameStr), t.stringLiteral(cx.fnName), t.stringLiteral("recomputed"), t.stringLiteral(loc), @@ -706,7 +723,9 @@ function codegenReactiveScope( ) ); idempotenceDetectionStatements.push( - t.expressionStatement(t.assignmentExpression("=", name, slot)) + t.expressionStatement( + t.assignmentExpression("=", t.identifier(nameStr), genSlot()) + ) ); } const condition = cx.synthesizeName("condition"); 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 a411753e1f7..aef67635f8c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -369,7 +369,7 @@ export function findDisjointMutableValues( const operands = collectMutableOperands( fn, instr, - fn.env.config.enableChangeDetectionForDebugging != null + fn.env.config.enableChangeDetection != null ); if (operands.length !== 0) { scopeIdentifiers.union(operands); diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts index 5796bd751fe..77145997192 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts @@ -813,7 +813,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor { memoizeJsxElements: !this.env.config.enableForest, forceMemoizePrimitives: this.env.config.enableForest || - this.env.config.enableChangeDetectionForDebugging != null, + this.env.config.enableChangeDetection != null, }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md index 099faadcede..c1235a8668c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableChangeDetectionForDebugging +// @enableChangeDetection function Component(props) { let x = null; if (props.cond) { @@ -18,7 +18,7 @@ function Component(props) { ```javascript import { $structuralCheck } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableChangeDetectionForDebugging +import { c as _c } from "react/compiler-runtime"; // @enableChangeDetection function Component(props) { const $ = _c(2); let x = null; @@ -30,6 +30,7 @@ function Component(props) { if (!condition) { let old$x = $[1]; $structuralCheck(old$x, x, "x", "Component", "cached", "(3:6)"); + x = old$x; } $[0] = props.value; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.js index 8ccc3d30f09..b74515c2a1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.js @@ -1,4 +1,4 @@ -// @enableChangeDetectionForDebugging +// @enableChangeDetection function Component(props) { let x = null; if (props.cond) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-wrapper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-wrapper.expect.md new file mode 100644 index 00000000000..3505aa2f950 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-wrapper.expect.md @@ -0,0 +1,51 @@ + +## Input + +```javascript +// @enableChangeDetectionWrappers +function Component(props) { + let x = null; + if (props.cond) { + x = []; + x.push(props.value); + } + return x; +} + +``` + +## Code + +```javascript +import { $structuralCheck, $store, $restore } from "react-compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enableChangeDetectionWrappers +function Component(props) { + const $ = _c(2); + let x = null; + if (props.cond) { + { + x = []; + x.push(props.value); + let condition = $[0] !== props.value; + if (!condition) { + let old$x = $[1]; + $structuralCheck(old$x, x, "x", "Component", "cached", "(3:6)"); + x = $restore(old$x); + } + $[0] = props.value; + $[1] = $store(x); + if (condition) { + x = []; + x.push(props.value); + $structuralCheck($[1], x, "x", "Component", "recomputed", "(3:6)"); + x = $[1]; + } + } + } + return x; +} + +``` + +### 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/change-detect-wrapper.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-wrapper.js new file mode 100644 index 00000000000..2de2564e6af --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-wrapper.js @@ -0,0 +1,9 @@ +// @enableChangeDetectionWrappers +function Component(props) { + let x = null; + if (props.cond) { + x = []; + x.push(props.value); + } + return x; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.expect.md deleted file mode 100644 index 73d664f5938..00000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.expect.md +++ /dev/null @@ -1,17 +0,0 @@ - -## Input - -```javascript -// @disableMemoizationForDebugging @enableChangeDetectionForDebugging -function Component(props) {} - -``` - - -## Error - -``` -InvalidConfig: Invalid environment config: the 'disableMemoizationForDebugging' and 'enableChangeDetectionForDebugging' options cannot be used together -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.js deleted file mode 100644 index ce93cd29f1a..00000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.js +++ /dev/null @@ -1,2 +0,0 @@ -// @disableMemoizationForDebugging @enableChangeDetectionForDebugging -function Component(props) {} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nomemo-and-change-detect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nomemo-and-change-detect.expect.md new file mode 100644 index 00000000000..cbe0c7fc678 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nomemo-and-change-detect.expect.md @@ -0,0 +1,128 @@ + +## Input + +```javascript +// @disableMemoizationForDebugging @enableChangeDetection +import { useMemo } from "react"; + +function Component(props) { + const a = useMemo(() =>
{props.a}
, [props]); + const b =
{props.b}
; + return ( +
+ {a} + {b} +
+ ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 1, b: 2 }], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { $structuralCheck } from "react-compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @disableMemoizationForDebugging @enableChangeDetection +import { useMemo } from "react"; + +function Component(props) { + const $ = _c(9); + let t0; + let t1; + { + t1 =
{props.a}
; + let condition = $[0] !== props; + if (!condition) { + let old$t1 = $[1]; + $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(5:5)"); + t1 = old$t1; + } + $[0] = props; + $[1] = t1; + if (condition) { + t1 =
{props.a}
; + $structuralCheck($[1], t1, "t1", "Component", "recomputed", "(5:5)"); + t1 = $[1]; + } + } + t0 = t1; + const a = t0; + let t2; + { + t2 = props.b; + let condition = $[2] !== props.b; + if (!condition) { + let old$t2 = $[3]; + $structuralCheck(old$t2, t2, "t2", "Component", "cached", "(6:6)"); + } + $[2] = props.b; + $[3] = t2; + if (condition) { + t2 = props.b; + $structuralCheck($[3], t2, "t2", "Component", "recomputed", "(6:6)"); + t2 = $[3]; + } + } + let t3; + { + t3 =
{t2}
; + let condition = $[4] !== t2; + if (!condition) { + let old$t3 = $[5]; + $structuralCheck(old$t3, t3, "t3", "Component", "cached", "(6:6)"); + } + $[4] = t2; + $[5] = t3; + if (condition) { + t3 =
{t2}
; + $structuralCheck($[5], t3, "t3", "Component", "recomputed", "(6:6)"); + t3 = $[5]; + } + } + const b = t3; + let t4; + { + t4 = ( +
+ {a} + {b} +
+ ); + let condition = $[6] !== a || $[7] !== b; + if (!condition) { + let old$t4 = $[8]; + $structuralCheck(old$t4, t4, "t4", "Component", "cached", "(8:11)"); + } + $[6] = a; + $[7] = b; + $[8] = t4; + if (condition) { + t4 = ( +
+ {a} + {b} +
+ ); + $structuralCheck($[8], t4, "t4", "Component", "recomputed", "(8:11)"); + t4 = $[8]; + } + } + return t4; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 1, b: 2 }], + isComponent: true, +}; + +``` + +### Eval output +(kind: ok)
1
2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nomemo-and-change-detect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nomemo-and-change-detect.js new file mode 100644 index 00000000000..8c53a732c38 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nomemo-and-change-detect.js @@ -0,0 +1,19 @@ +// @disableMemoizationForDebugging @enableChangeDetection +import { useMemo } from "react"; + +function Component(props) { + const a = useMemo(() =>
{props.a}
, [props]); + const b =
{props.b}
; + return ( +
+ {a} + {b} +
+ ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 1, b: 2 }], + isComponent: true, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md index db34cac6e41..0323d4ffffa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -import { useState } from "react"; // @enableChangeDetectionForDebugging +import { useState } from "react"; // @enableChangeDetection function useOther(x) { return x; @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { $structuralCheck } from "react-compiler-runtime"; import { c as _c } from "react/compiler-runtime"; -import { useState } from "react"; // @enableChangeDetectionForDebugging +import { useState } from "react"; // @enableChangeDetection function useOther(x) { return x; @@ -47,6 +47,7 @@ function Component(props) { if (!condition) { let old$t0 = $[1]; $structuralCheck(old$t0, t0, "t0", "Component", "cached", "(8:8)"); + t0 = old$t0; } $[0] = props.x; $[1] = t0; @@ -63,6 +64,7 @@ function Component(props) { if (!condition) { let old$t1 = $[3]; $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(8:8)"); + t1 = old$t1; } $[2] = t0; $[3] = t1; @@ -82,6 +84,7 @@ function Component(props) { if (!condition) { let old$x = $[5]; $structuralCheck(old$x, x, "x", "Component", "cached", "(10:10)"); + x = old$x; } $[4] = t2; $[5] = x; @@ -98,6 +101,7 @@ function Component(props) { if (!condition) { let old$t3 = $[7]; $structuralCheck(old$t3, t3, "t3", "Component", "cached", "(11:11)"); + t3 = old$t3; } $[6] = x; $[7] = t3; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.js index 4f57f785d90..951f3544d88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.js @@ -1,4 +1,4 @@ -import { useState } from "react"; // @enableChangeDetectionForDebugging +import { useState } from "react"; // @enableChangeDetection function useOther(x) { return x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md index 93776908569..da07a56e963 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableChangeDetectionForDebugging +// @enableChangeDetection import { useState } from "react"; function Component(props) { @@ -16,7 +16,7 @@ function Component(props) { ```javascript import { $structuralCheck } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableChangeDetectionForDebugging +import { c as _c } from "react/compiler-runtime"; // @enableChangeDetection import { useState } from "react"; function Component(props) { @@ -43,6 +43,7 @@ function Component(props) { if (!condition) { let old$x = $[3]; $structuralCheck(old$x, x, "x", "Component", "cached", "(5:5)"); + x = old$x; } $[2] = t2; $[3] = x; @@ -59,6 +60,7 @@ function Component(props) { if (!condition) { let old$t3 = $[5]; $structuralCheck(old$t3, t3, "t3", "Component", "cached", "(6:6)"); + t3 = old$t3; } $[4] = x; $[5] = t3; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.js index 46a9c23fe94..bbfd239cbdd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.js @@ -1,4 +1,4 @@ -// @enableChangeDetectionForDebugging +// @enableChangeDetection import { useState } from "react"; function Component(props) { 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 9d107a2a432..dec1cd615d8 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 @@ -2,7 +2,7 @@ ## Input ```javascript -import { useState } from "react"; // @enableChangeDetectionForDebugging +import { useState } from "react"; // @enableChangeDetection function Component(props) { const w = f(props.x); @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { $structuralCheck } from "react-compiler-runtime"; import { c as _c } from "react/compiler-runtime"; -import { useState } from "react"; // @enableChangeDetectionForDebugging +import { useState } from "react"; // @enableChangeDetection function Component(props) { const $ = _c(9); @@ -43,6 +43,7 @@ function Component(props) { if (!condition) { let old$t0 = $[1]; $structuralCheck(old$t0, t0, "t0", "Component", "cached", "(4:4)"); + t0 = old$t0; } $[0] = props.x; $[1] = t0; @@ -59,6 +60,7 @@ function Component(props) { if (!condition) { let old$t1 = $[3]; $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(4:4)"); + t1 = old$t1; } $[2] = t0; $[3] = t1; @@ -77,6 +79,7 @@ function Component(props) { if (!condition) { let old$x = $[5]; $structuralCheck(old$x, x, "x", "Component", "cached", "(5:5)"); + x = old$x; } $[4] = t2; $[5] = x; @@ -98,6 +101,7 @@ function Component(props) { if (!condition) { let old$t3 = $[8]; $structuralCheck(old$t3, t3, "t3", "Component", "cached", "(7:10)"); + t3 = old$t3; } $[6] = x; $[7] = w; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.js index c63c16aebc4..153b2bbe34f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.js @@ -1,4 +1,4 @@ -import { useState } from "react"; // @enableChangeDetectionForDebugging +import { useState } from "react"; // @enableChangeDetection function Component(props) { const w = f(props.x); diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index e575ca0a3ea..889514bd348 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -45,7 +45,7 @@ function makePluginOptions( let hookPattern: string | null = null; // TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false let validatePreserveExistingMemoizationGuarantees = false; - let enableChangeDetectionForDebugging = null; + let enableChangeDetection = null; let enablePreserveExistingManualUseMemo: "hook" | "scope" | null = null; let customMacros = null; @@ -125,10 +125,20 @@ function makePluginOptions( validatePreserveExistingMemoizationGuarantees = true; } - if (firstLine.includes("@enableChangeDetectionForDebugging")) { - enableChangeDetectionForDebugging = { + if (firstLine.includes("@enableChangeDetection")) { + enableChangeDetection = { source: "react-compiler-runtime", - importSpecifierName: "$structuralCheck", + structuralCheck: "$structuralCheck", + }; + } + if (firstLine.includes("@enableChangeDetectionWrappers")) { + enableChangeDetection = { + source: "react-compiler-runtime", + structuralCheck: "$structuralCheck", + wrappers: { + store: "$store", + restore: "$restore", + }, }; } if (firstLine.includes("@enablePreserveExistingManualUseMemoAsHook")) { @@ -218,7 +228,7 @@ function makePluginOptions( enableSharedRuntime__testonly: true, hookPattern, validatePreserveExistingMemoizationGuarantees, - enableChangeDetectionForDebugging, + enableChangeDetection, enablePreserveExistingManualUseMemo, }, compilationMode,