,
+ () => {
+ 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);
}
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 684acaf298388..75bd8f6811ffb 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/Optimization/OptimizePropsMethodCalls.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OptimizePropsMethodCalls.ts
new file mode 100644
index 0000000000000..ab686ca219315
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OptimizePropsMethodCalls.ts
@@ -0,0 +1,52 @@
+/**
+ * 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 {HIRFunction, isPropsType} from '../HIR';
+
+/**
+ * Converts method calls into regular calls where the receiver is the props object:
+ *
+ * Example:
+ *
+ * ```
+ * // INPUT
+ * props.foo();
+ *
+ * // OUTPUT
+ * const t0 = props.foo;
+ * t0();
+ * ```
+ *
+ * Counter example:
+ *
+ * Here the receiver is `props.foo`, not the props object, so we don't rewrite it:
+ *
+ * // INPUT
+ * props.foo.bar();
+ *
+ * // OUTPUT
+ * props.foo.bar();
+ * ```
+ */
+export function optimizePropsMethodCalls(fn: HIRFunction): void {
+ for (const [, block] of fn.body.blocks) {
+ for (let i = 0; i < block.instructions.length; i++) {
+ const instr = block.instructions[i]!;
+ if (
+ instr.value.kind === 'MethodCall' &&
+ isPropsType(instr.value.receiver.identifier)
+ ) {
+ instr.value = {
+ kind: 'CallExpression',
+ callee: instr.value.property,
+ args: instr.value.args,
+ loc: instr.value.loc,
+ };
+ }
+ }
+ }
+}
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 098139b150d5a..1108422f070d7 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 fa43a8befeb83..0000000000000
--- 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/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 0000000000000..b8c7f8d4225f7
--- /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 0000000000000..ca7076fda4019
--- /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/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 2b0031b117be2..f8712ed7289a9 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 7febb3fecb6e7..1268cbcfdc3d3 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 26b56ea2a4f4d..769e4871f4ad8 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 5ffa73389ffd0..e66ef2df13d5f 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 2c495d8223d0b..66799c5c4720b 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/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 b84229156bc14..108c6725f7e8a 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
@@ -3,7 +3,7 @@
```javascript
// @enableJsxOutlining
-function Component(arr) {
+function Component({arr}) {
const x = useX();
return arr.map(i => {
<>
@@ -49,12 +49,13 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
-function Component(arr) {
+function Component(t0) {
const $ = _c(3);
+ const { arr } = t0;
const x = useX();
- let t0;
+ let t1;
if ($[0] !== arr || $[1] !== x) {
- t0 = arr.map((i) => {
+ t1 = arr.map((i) => {
arr.map((i_0, id) => {
const T0 = _temp;
const child = ;
@@ -65,11 +66,11 @@ function Component(arr) {
});
$[0] = arr;
$[1] = x;
- $[2] = t0;
+ $[2] = t1;
} else {
- t0 = $[2];
+ t1 = $[2];
}
- return t0;
+ return t1;
}
function _temp(t0) {
const $ = _c(5);
@@ -140,4 +141,4 @@ export const FIXTURE_ENTRYPOINT = {
```
### Eval output
-(kind: exception) arr.map is not a function
\ No newline at end of file
+(kind: ok) [null,null]
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.js
index b7a82cd2a4be2..96a4e7bb2484b 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.js
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.js
@@ -1,5 +1,5 @@
// @enableJsxOutlining
-function Component(arr) {
+function Component({arr}) {
const x = useX();
return arr.map(i => {
<>
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 dfe941282e2a3..d34db46d6aa28 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 ae44f27912293..0000000000000
--- 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 0000000000000..a1cbe89a88340
--- /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 dc1a87fe5113c..e8a3e2d627c59 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 39f301432e51f..9d232d8e78169 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/props-method-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md
new file mode 100644
index 0000000000000..3297892ea2469
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md
@@ -0,0 +1,78 @@
+
+## Input
+
+```javascript
+// @compilationMode(infer)
+import {useMemo} from 'react';
+import {ValidateMemoization} from 'shared-runtime';
+
+function Component(props) {
+ const x = useMemo(() => props.x(), [props.x]);
+ return ;
+}
+
+const f = () => ['React'];
+const g = () => ['Compiler'];
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{x: () => ['React']}],
+ sequentialRenders: [{x: f}, {x: g}, {x: g}, {x: f}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
+import { useMemo } from "react";
+import { ValidateMemoization } from "shared-runtime";
+
+function Component(props) {
+ const $ = _c(7);
+ let t0;
+ let t1;
+ if ($[0] !== props.x) {
+ t1 = props.x();
+ $[0] = props.x;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ t0 = t1;
+ const x = t0;
+ let t2;
+ if ($[2] !== props.x) {
+ t2 = [props.x];
+ $[2] = props.x;
+ $[3] = t2;
+ } else {
+ t2 = $[3];
+ }
+ let t3;
+ if ($[4] !== t2 || $[5] !== x) {
+ t3 = ;
+ $[4] = t2;
+ $[5] = x;
+ $[6] = t3;
+ } else {
+ t3 = $[6];
+ }
+ return t3;
+}
+
+const f = () => ["React"];
+const g = () => ["Compiler"];
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ x: () => ["React"] }],
+ sequentialRenders: [{ x: f }, { x: g }, { x: g }, { x: f }],
+};
+
+```
+
+### Eval output
+(kind: ok) {"inputs":["[[ function params=0 ]]"],"output":["React"]}
+{"inputs":["[[ function params=0 ]]"],"output":["Compiler"]}
+{"inputs":["[[ function params=0 ]]"],"output":["Compiler"]}
+{"inputs":["[[ function params=0 ]]"],"output":["React"]}
\ No newline at end of file
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
new file mode 100644
index 0000000000000..4c2d322ad3399
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js
@@ -0,0 +1,16 @@
+// @compilationMode(infer)
+import {useMemo} from 'react';
+import {ValidateMemoization} from 'shared-runtime';
+
+function Component(props) {
+ const x = useMemo(() => props.x(), [props.x]);
+ return ;
+}
+
+const f = () => ['React'];
+const g = () => ['Compiler'];
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{x: () => ['React']}],
+ sequentialRenders: [{x: f}, {x: g}, {x: g}, {x: f}],
+};
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 23cc7ee84607b..ceaa350012258 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 0000000000000..d72f34b4fd8a9
--- /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 0000000000000..b9bdd67e2f504
--- /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 d8e59c486a55b..b7c425ba5c027 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/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 0000000000000..e491eb6c69d3c
--- /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 0000000000000..9dbaac79c6b5e
--- /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}],
+};
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 0000000000000..69c1b9bbbbc73
--- /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 0000000000000..9b314e1f99d53
--- /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__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md
index d94a5e7e375b3..e335273026791 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/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts
index d634bd235f190..dc4d5d25a47e2 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 150df26e45818..188c244d9ef2c 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/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts
index 36b3e92f9636b..361070739630c 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',
diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts
index 1cb8fe48b9451..a95c61450d840 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}>] {
@@ -56,6 +58,7 @@ function makePluginOptions(
let validatePreserveExistingMemoizationGuarantees = false;
let customMacros: null | Array = null;
let validateBlocklistedImports = null;
+ let enableFire = false;
let target: CompilerReactTarget = '19';
if (firstLine.indexOf('@compilationMode(annotation)') !== -1) {
@@ -127,6 +130,10 @@ function makePluginOptions(
validatePreserveExistingMemoizationGuarantees = true;
}
+ if (firstLine.includes('@enableFire')) {
+ enableFire = true;
+ }
+
const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine);
if (
hookPatternMatch &&
@@ -182,15 +189,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 = {
@@ -205,6 +212,7 @@ function makePluginOptions(
hookPattern,
validatePreserveExistingMemoizationGuarantees,
validateBlocklistedImports,
+ enableFire,
},
compilationMode,
logger,
@@ -338,6 +346,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 +374,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 abee06c55be8a..ad77441b532df 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 f05757d3df68d..ea87cd1e91d16 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,
);
diff --git a/compiler/packages/snap/src/sprout/index.ts b/compiler/packages/snap/src/sprout/index.ts
index 733be561c08a8..04748bed28f4f 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 58815842cb03c..1b8648f4ff031 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(
diff --git a/fixtures/flight/src/App.js b/fixtures/flight/src/App.js
index 49bfc9e05135c..69fbb5e0af97d 100644
--- a/fixtures/flight/src/App.js
+++ b/fixtures/flight/src/App.js
@@ -27,7 +27,8 @@ function Foo({children}) {
return {children}
;
}
-function Bar({children}) {
+async function Bar({children}) {
+ await new Promise(resolve => setTimeout(() => resolve('deferred text'), 10));
return {children}
;
}
@@ -81,7 +82,7 @@ export default async function App({prerender}) {
{dedupedChild}
- {dedupedChild}
+ {Promise.resolve([dedupedChild])}