Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ function* runWithEnvironment(

if (
!env.config.enablePreserveExistingManualUseMemo &&
!env.config.disableMemoizationForDebugging
!env.config.disableMemoizationForDebugging &&
!env.config.enableChangeDetectionForDebugging
) {
dropManualMemoization(hir);
yield log({ kind: "hir", name: "DropManualMemoization", value: hir });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,13 @@ export function compileProgram(
);
externalFunctions.push(enableEmitHookGuards);
}

if (options.environment?.enableChangeDetectionForDebugging != null) {
const enableChangeDetectionForDebugging = tryParseExternalFunction(
options.environment.enableChangeDetectionForDebugging
);
externalFunctions.push(enableChangeDetectionForDebugging);
}
} catch (err) {
handleError(err, pass, null);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,14 @@ const EnvironmentConfigSchema = z.object({
*/
disableMemoizationForDebugging: z.boolean().default(false),

/**
* When true, rather using memoized values, the compiler will always re-compute
* values, and then use a heuristic to compare the memoized value to the newly
* computed one. This detects cases where rules of react violations may cause the
* compiled code to behave differently than the original.
*/
enableChangeDetectionForDebugging: ExternalFunctionSchema.nullish(),

/**
* The react native re-animated library uses custom Babel transforms that
* requires the calls to library API remain unmodified.
Expand Down Expand Up @@ -478,6 +486,18 @@ 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}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,22 +617,83 @@ function codegenReactiveScope(
}

if (cx.env.config.disableMemoizationForDebugging) {
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,
}
);
testCondition = t.logicalExpression(
"||",
testCondition,
t.booleanLiteral(true)
);
}

let computationBlock = codegenBlock(cx, block);
computationBlock.body.push(...cacheStoreStatements);
let memoStatement;
const memoBlock = t.blockStatement(cacheLoadStatements);
if (
cx.env.config.enableChangeDetectionForDebugging != null &&
changeExpressions.length > 0
) {
const detectionFunction =
cx.env.config.enableChangeDetectionForDebugging.importSpecifierName;
const changeDetectionStatements: Array<t.Statement> = [];
const oldVarDeclarationStatements: Array<t.Statement> = [];
memoBlock.body.forEach((stmt) => {
if (
stmt.type === "ExpressionStatement" &&
stmt.expression.type === "AssignmentExpression" &&
stmt.expression.left.type === "Identifier"
) {
const name = stmt.expression.left.name;
const loadName = cx.synthesizeName(`old$${name}`);
oldVarDeclarationStatements.push(
t.variableDeclaration("let", [
t.variableDeclarator(t.identifier(loadName)),
])
);
stmt.expression.left = t.identifier(loadName);
changeDetectionStatements.push(
t.expressionStatement(
t.callExpression(t.identifier(detectionFunction), [
t.identifier(loadName),
t.identifier(name),
t.stringLiteral(name),
t.stringLiteral(cx.fnName),
])
)
);
changeDetectionStatements.push(
t.expressionStatement(
t.assignmentExpression(
"=",
t.identifier(name),
t.identifier(loadName)
)
)
);
}
});
memoStatement = t.blockStatement([
...computationBlock.body,
t.ifStatement(
t.unaryExpression("!", testCondition),
t.blockStatement([
...oldVarDeclarationStatements,
...memoBlock.body,
...changeDetectionStatements,
])
),
...cacheStoreStatements,
]);
} else {
computationBlock.body.push(...cacheStoreStatements);

const memoStatement = t.ifStatement(
testCondition,
computationBlock,
memoBlock
);
memoStatement = t.ifStatement(testCondition, computationBlock, memoBlock);
}

if (cx.env.config.enableMemoizationComments) {
if (changeExpressionComments.length) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

## Input

```javascript
// @disableMemoizationForDebugging @enableChangeDetectionForDebugging
function Component(props) {}

```


## Error

```
InvalidConfig: Invalid environment config: the 'disableMemoizationForDebugging' and 'enableChangeDetectionForDebugging' options cannot be used together
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @disableMemoizationForDebugging @enableChangeDetectionForDebugging
function Component(props) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

## Input

```javascript
// @enableChangeDetectionForDebugging
import { useState } from "react";

function Component(props) {
const [x, _] = useState(f(props.x));
return <div>{x}</div>;
}

```

## Code

```javascript
import { $structuralCheck } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableChangeDetectionForDebugging
import { useState } from "react";

function Component(props) {
const $ = _c(4);
let t0;
{
t0 = f(props.x);
if (!($[0] !== props.x)) {
let old$t0;
old$t0 = $[1];
$structuralCheck(old$t0, t0, "t0", "Component");
t0 = old$t0;
}
$[0] = props.x;
$[1] = t0;
}
const [x] = useState(t0);
let t1;
{
t1 = <div>{x}</div>;
if (!($[2] !== x)) {
let old$t1;
old$t1 = $[3];
$structuralCheck(old$t1, t1, "t1", "Component");
t1 = old$t1;
}
$[2] = x;
$[3] = t1;
}
return t1;
}

```

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @enableChangeDetectionForDebugging
import { useState } from "react";

function Component(props) {
const [x, _] = useState(f(props.x));
return <div>{x}</div>;
}
Loading