@samitouri / QOS-React / commits / d92e5713be

[compiler] Avoid bailouts when inserting gating (#32598)

This change fixes a coverage hole in rolling out with `gating`. Prior to this PR, configuring `gating` causes React Compiler to bail out of optimizing some functions. This means that it's not entirely safe to cutover from `gating` enabled for all users (i.e. rolled out 100%) to removing the `gating` config altogether, as new functions may be opted into compilation when they stop bailing out due to gating-specific logic. This is technically slightly slower due to the additional function indirection. An alternative approach is to recommend running a codemod to insert `use no memo`s on currently-bailing out functions before removing the`gating` config. --- Tested [internally]( https://fburl.com/diff/q982ovua) by enabling on a page that previously had a few hundred bailouts due to gating + hoisted function declarations and (1) clicking around locally and (2) running a bunch of e2e tests

mofeiZ committed Mar 13, 2025 at 19:31 UTC d92e5713be2dc78f467c31fce4a1e5c84a74e4e6
17 files changed +526 -96
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Gating.ts
+117 -8
@@ -10,6 +10,117 @@ import * as t from '@babel/types';
10 import {PluginOptions} from './Options';
11 import {CompilerError} from '../CompilerError';
12
13 +/**
14 + * Gating rewrite for function declarations which are referenced before their
15 + * declaration site.
16 + *
17 + * ```js
18 + * // original
19 + * export default React.memo(Foo);
20 + * function Foo() { ... }
21 + *
22 + * // React compiler optimized + gated
23 + * import {gating} from 'myGating';
24 + * export default React.memo(Foo);
25 + * const gating_result = gating(); <- inserted
26 + * function Foo_optimized() {} <- inserted
27 + * function Foo_unoptimized() {} <- renamed from Foo
28 + * function Foo() { <- inserted function, which can be hoisted by JS engines
29 + * if (gating_result) return Foo_optimized();
30 + * else return Foo_unoptimized();
31 + * }
32 + * ```
33 + */
34 +function insertAdditionalFunctionDeclaration(
35 + fnPath: NodePath<t.FunctionDeclaration>,
36 + compiled: t.FunctionDeclaration,
37 + gating: NonNullable<PluginOptions['gating']>,
38 +): void {
39 + const originalFnName = fnPath.node.id;
40 + const originalFnParams = fnPath.node.params;
41 + const compiledParams = fnPath.node.params;
42 + /**
43 + * Note that other than `export default function() {}`, all other function
44 + * declarations must have a binding identifier. Since default exports cannot
45 + * be referenced, it's safe to assume that all function declarations passed
46 + * here will have an identifier.
47 + * https://tc39.es/ecma262/multipage/ecmascript-language-functions-and-classes.html#sec-function-definitions
48 + */
49 + CompilerError.invariant(originalFnName != null && compiled.id != null, {
50 + reason:
51 + 'Expected function declarations that are referenced elsewhere to have a named identifier',
52 + loc: fnPath.node.loc ?? null,
53 + });
54 + CompilerError.invariant(originalFnParams.length === compiledParams.length, {
55 + reason:
56 + 'Expected React Compiler optimized function declarations to have the same number of parameters as source',
57 + loc: fnPath.node.loc ?? null,
58 + });
59 +
60 + const gatingCondition = fnPath.scope.generateUidIdentifier(
61 + `${gating.importSpecifierName}_result`,
62 + );
63 + const unoptimizedFnName = fnPath.scope.generateUidIdentifier(
64 + `${originalFnName.name}_unoptimized`,
65 + );
66 + const optimizedFnName = fnPath.scope.generateUidIdentifier(
67 + `${originalFnName.name}_optimized`,
68 + );
69 + /**
70 + * Step 1: rename existing functions
71 + */
72 + compiled.id.name = optimizedFnName.name;
73 + fnPath.get('id').replaceInline(unoptimizedFnName);
74 +
75 + /**
76 + * Step 2: insert new function declaration
77 + */
78 + const newParams: Array<t.Identifier | t.RestElement> = [];
79 + const genNewArgs: Array<() => t.Identifier | t.SpreadElement> = [];
80 + for (let i = 0; i < originalFnParams.length; i++) {
81 + const argName = `arg${i}`;
82 + if (originalFnParams[i].type === 'RestElement') {
83 + newParams.push(t.restElement(t.identifier(argName)));
84 + genNewArgs.push(() => t.spreadElement(t.identifier(argName)));
85 + } else {
86 + newParams.push(t.identifier(argName));
87 + genNewArgs.push(() => t.identifier(argName));
88 + }
89 + }
90 + // insertAfter called in reverse order of how nodes should appear in program
91 + fnPath.insertAfter(
92 + t.functionDeclaration(
93 + originalFnName,
94 + newParams,
95 + t.blockStatement([
96 + t.ifStatement(
97 + gatingCondition,
98 + t.returnStatement(
99 + t.callExpression(
100 + compiled.id,
101 + genNewArgs.map(fn => fn()),
102 + ),
103 + ),
104 + t.returnStatement(
105 + t.callExpression(
106 + unoptimizedFnName,
107 + genNewArgs.map(fn => fn()),
108 + ),
109 + ),
110 + ),
111 + ]),
112 + ),
113 + );
114 + fnPath.insertBefore(
115 + t.variableDeclaration('const', [
116 + t.variableDeclarator(
117 + gatingCondition,
118 + t.callExpression(t.identifier(gating.importSpecifierName), []),
119 + ),
120 + ]),
121 + );
122 + fnPath.insertBefore(compiled);
123 +}
124 export function insertGatedFunctionDeclaration(
125 fnPath: NodePath<
126 t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
@@ -21,15 +132,13 @@ export function insertGatedFunctionDeclaration(
132 gating: NonNullable<PluginOptions['gating']>,
133 referencedBeforeDeclaration: boolean,
134 ): void {
24 - if (referencedBeforeDeclaration) {
25 - const identifier =
26 - fnPath.node.type === 'FunctionDeclaration' ? fnPath.node.id : null;
27 - CompilerError.invariant(false, {
28 - reason: `Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting`,
29 - description: `Rewrite the reference to ${identifier?.name ?? 'this function'} to not rely on hoisting to fix this issue`,
30 - loc: identifier?.loc ?? null,
31 - suggestions: null,
135 + if (referencedBeforeDeclaration && fnPath.isFunctionDeclaration()) {
136 + CompilerError.invariant(compiled.type === 'FunctionDeclaration', {
137 + reason: 'Expected compiled node type to match input type',
138 + description: `Got ${compiled.type} but expected FunctionDeclaration`,
139 + loc: fnPath.node.loc ?? null,
140 });
141 + insertAdditionalFunctionDeclaration(fnPath, compiled, gating);
142 } else {
143 const gatingExpression = t.conditionalExpression(
144 t.callExpression(t.identifier(gating.importSpecifierName), []),
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/component-syntax-ref-gating.flow.expect.md new
+62
@@ -0,0 +1,62 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @flow @gating
6 +import {Stringify} from 'shared-runtime';
7 +import * as React from 'react';
8 +
9 +component Foo(ref: React.RefSetter<Controls>) {
10 + return <Stringify ref={ref} />;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: eval('(...args) => React.createElement(Foo, args)'),
15 + params: [{ref: React.createRef()}],
16 +};
17 +
18 +```
19 +
20 +## Code
21 +
22 +```javascript
23 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
24 +import { c as _c } from "react/compiler-runtime";
25 +import { Stringify } from "shared-runtime";
26 +import * as React from "react";
27 +
28 +const Foo = React.forwardRef(Foo_withRef);
29 +const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
30 +function _Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
31 + const $ = _c(2);
32 + let t0;
33 + if ($[0] !== ref) {
34 + t0 = <Stringify ref={ref} />;
35 + $[0] = ref;
36 + $[1] = t0;
37 + } else {
38 + t0 = $[1];
39 + }
40 + return t0;
41 +}
42 +function _Foo_withRef_unoptimized(
43 + _$$empty_props_placeholder$$: $ReadOnly<{}>,
44 + ref: React.RefSetter<Controls>,
45 +): React.Node {
46 + return <Stringify ref={ref} />;
47 +}
48 +function Foo_withRef(arg0, arg1) {
49 + if (_isForgetEnabled_Fixtures_result)
50 + return _Foo_withRef_optimized(arg0, arg1);
51 + else return _Foo_withRef_unoptimized(arg0, arg1);
52 +}
53 +
54 +export const FIXTURE_ENTRYPOINT = {
55 + fn: eval("(...args) => React.createElement(Foo, args)"),
56 + params: [{ ref: React.createRef() }],
57 +};
58 +
59 +```
60 +
61 +### Eval output
62 +(kind: ok) <div>{"ref":null}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/component-syntax-ref-gating.flow.js new
+12
@@ -0,0 +1,12 @@
1 +// @flow @gating
2 +import {Stringify} from 'shared-runtime';
3 +import * as React from 'react';
4 +
5 +component Foo(ref: React.RefSetter<Controls>) {
6 + return <Stringify ref={ref} />;
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: eval('(...args) => React.createElement(Foo, args)'),
11 + params: [{ref: React.createRef()}],
12 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.component-syntax-ref-gating.flow.expect.md deleted
-24
@@ -1,24 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @flow @gating
6 -component Foo(ref: React.RefSetter<Controls>) {
7 - return <Bar ref={ref} />;
8 -}
9 -
10 -```
11 -
12 -
13 -## Error
14 -
15 -```
16 - 1 | // @flow @gating
17 -> 2 | component Foo(ref: React.RefSetter<Controls>) {
18 - | ^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo_withRef to not rely on hoisting to fix this issue (2:2)
19 - 3 | return <Bar ref={ref} />;
20 - 4 | }
21 - 5 |
22 -```
23 -
24 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.component-syntax-ref-gating.flow.js deleted
-4
@@ -1,4 +0,0 @@
1 -// @flow @gating
2 -component Foo(ref: React.RefSetter<Controls>) {
3 - return <Bar ref={ref} />;
4 -}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.gating-hoisting.expect.md deleted
-26
@@ -1,26 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @gating
6 -const Foo = React.forwardRef(Foo_withRef);
7 -function Foo_withRef(props, ref) {
8 - return <Bar ref={ref} {...props}></Bar>;
9 -}
10 -
11 -```
12 -
13 -
14 -## Error
15 -
16 -```
17 - 1 | // @gating
18 - 2 | const Foo = React.forwardRef(Foo_withRef);
19 -> 3 | function Foo_withRef(props, ref) {
20 - | ^^^^^^^^^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo_withRef to not rely on hoisting to fix this issue (3:3)
21 - 4 | return <Bar ref={ref} {...props}></Bar>;
22 - 5 | }
23 - 6 |
24 -```
25 -
26 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.gating-hoisting.js deleted
-5
@@ -1,5 +0,0 @@
1 -// @gating
2 -const Foo = React.forwardRef(Foo_withRef);
3 -function Foo_withRef(props, ref) {
4 - return <Bar ref={ref} {...props}></Bar>;
5 -}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.gating-use-before-decl.expect.md deleted
-24
@@ -1,24 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @gating
6 -import {memo} from 'react';
7 -
8 -export default memo(Foo);
9 -function Foo() {}
10 -
11 -```
12 -
13 -
14 -## Error
15 -
16 -```
17 - 3 |
18 - 4 | export default memo(Foo);
19 -> 5 | function Foo() {}
20 - | ^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo to not rely on hoisting to fix this issue (5:5)
21 - 6 |
22 -```
23 -
24 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.gating-use-before-decl.js deleted
-5
@@ -1,5 +0,0 @@
1 -// @gating
2 -import {memo} from 'react';
3 -
4 -export default memo(Foo);
5 -function Foo() {}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl-ref.expect.md new
+61
@@ -0,0 +1,61 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @gating
6 +import {createRef, forwardRef} from 'react';
7 +import {Stringify} from 'shared-runtime';
8 +
9 +const Foo = forwardRef(Foo_withRef);
10 +function Foo_withRef(props, ref) {
11 + return <Stringify ref={ref} {...props} />;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: eval('(...args) => React.createElement(Foo, args)'),
16 + params: [{prop1: 1, prop2: 2, ref: createRef()}],
17 +};
18 +
19 +```
20 +
21 +## Code
22 +
23 +```javascript
24 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
25 +import { c as _c } from "react/compiler-runtime"; // @gating
26 +import { createRef, forwardRef } from "react";
27 +import { Stringify } from "shared-runtime";
28 +
29 +const Foo = forwardRef(Foo_withRef);
30 +const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
31 +function _Foo_withRef_optimized(props, ref) {
32 + const $ = _c(3);
33 + let t0;
34 + if ($[0] !== props || $[1] !== ref) {
35 + t0 = <Stringify ref={ref} {...props} />;
36 + $[0] = props;
37 + $[1] = ref;
38 + $[2] = t0;
39 + } else {
40 + t0 = $[2];
41 + }
42 + return t0;
43 +}
44 +function _Foo_withRef_unoptimized(props, ref) {
45 + return <Stringify ref={ref} {...props} />;
46 +}
47 +function Foo_withRef(arg0, arg1) {
48 + if (_isForgetEnabled_Fixtures_result)
49 + return _Foo_withRef_optimized(arg0, arg1);
50 + else return _Foo_withRef_unoptimized(arg0, arg1);
51 +}
52 +
53 +export const FIXTURE_ENTRYPOINT = {
54 + fn: eval("(...args) => React.createElement(Foo, args)"),
55 + params: [{ prop1: 1, prop2: 2, ref: createRef() }],
56 +};
57 +
58 +```
59 +
60 +### Eval output
61 +(kind: ok) <div>{"0":{"prop1":1,"prop2":2,"ref":{"current":null}},"ref":"[[ cyclic ref *3 ]]"}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl-ref.js new
+13
@@ -0,0 +1,13 @@
1 +// @gating
2 +import {createRef, forwardRef} from 'react';
3 +import {Stringify} from 'shared-runtime';
4 +
5 +const Foo = forwardRef(Foo_withRef);
6 +function Foo_withRef(props, ref) {
7 + return <Stringify ref={ref} {...props} />;
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: eval('(...args) => React.createElement(Foo, args)'),
12 + params: [{prop1: 1, prop2: 2, ref: createRef()}],
13 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl.expect.md new
+64
@@ -0,0 +1,64 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @gating
6 +import {memo} from 'react';
7 +import {Stringify} from 'shared-runtime';
8 +
9 +export default memo(Foo);
10 +function Foo({prop1, prop2}) {
11 + 'use memo';
12 + return <Stringify prop1={prop1} prop2={prop2} />;
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: eval('Foo'),
17 + params: [{prop1: 1, prop2: 2}],
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
26 +import { c as _c } from "react/compiler-runtime"; // @gating
27 +import { memo } from "react";
28 +import { Stringify } from "shared-runtime";
29 +
30 +export default memo(Foo);
31 +const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
32 +function _Foo_optimized(t0) {
33 + "use memo";
34 + const $ = _c(3);
35 + const { prop1, prop2 } = t0;
36 + let t1;
37 + if ($[0] !== prop1 || $[1] !== prop2) {
38 + t1 = <Stringify prop1={prop1} prop2={prop2} />;
39 + $[0] = prop1;
40 + $[1] = prop2;
41 + $[2] = t1;
42 + } else {
43 + t1 = $[2];
44 + }
45 + return t1;
46 +}
47 +function _Foo_unoptimized({ prop1, prop2 }) {
48 + "use memo";
49 + return <Stringify prop1={prop1} prop2={prop2} />;
50 +}
51 +function Foo(arg0) {
52 + if (_isForgetEnabled_Fixtures_result) return _Foo_optimized(arg0);
53 + else return _Foo_unoptimized(arg0);
54 +}
55 +
56 +export const FIXTURE_ENTRYPOINT = {
57 + fn: eval("Foo"),
58 + params: [{ prop1: 1, prop2: 2 }],
59 +};
60 +
61 +```
62 +
63 +### Eval output
64 +(kind: ok) <div>{"prop1":1,"prop2":2}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl.js new
+14
@@ -0,0 +1,14 @@
1 +// @gating
2 +import {memo} from 'react';
3 +import {Stringify} from 'shared-runtime';
4 +
5 +export default memo(Foo);
6 +function Foo({prop1, prop2}) {
7 + 'use memo';
8 + return <Stringify prop1={prop1} prop2={prop2} />;
9 +}
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: eval('Foo'),
13 + params: [{prop1: 1, prop2: 2}],
14 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/invalid-fnexpr-reference.expect.md new
+59
@@ -0,0 +1,59 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @gating
6 +import * as React from 'react';
7 +
8 +let Foo;
9 +const MemoFoo = React.memo(Foo);
10 +Foo = () => <div>hello world!</div>;
11 +
12 +/**
13 + * Evaluate this fixture module to assert that compiler + original have the same
14 + * runtime error message.
15 + */
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: () => {},
18 + params: [],
19 +};
20 +
21 +```
22 +
23 +## Code
24 +
25 +```javascript
26 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
27 +import { c as _c } from "react/compiler-runtime"; // @gating
28 +import * as React from "react";
29 +
30 +let Foo;
31 +const MemoFoo = React.memo(Foo);
32 +Foo = isForgetEnabled_Fixtures()
33 + ? () => {
34 + const $ = _c(1);
35 + let t0;
36 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
37 + t0 = <div>hello world!</div>;
38 + $[0] = t0;
39 + } else {
40 + t0 = $[0];
41 + }
42 + return t0;
43 + }
44 + : () => <div>hello world!</div>;
45 +
46 +/**
47 + * Evaluate this fixture module to assert that compiler + original have the same
48 + * runtime error message.
49 + */
50 +export const FIXTURE_ENTRYPOINT = {
51 + fn: isForgetEnabled_Fixtures() ? () => {} : () => {},
52 + params: [],
53 +};
54 +
55 +```
56 +
57 +### Eval output
58 +(kind: ok)
59 +logs: ['memo: The first argument must be a component. Instead received: %s','undefined']
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/invalid-fnexpr-reference.js new
+15
@@ -0,0 +1,15 @@
1 +// @gating
2 +import * as React from 'react';
3 +
4 +let Foo;
5 +const MemoFoo = React.memo(Foo);
6 +Foo = () => <div>hello world!</div>;
7 +
8 +/**
9 + * Evaluate this fixture module to assert that compiler + original have the same
10 + * runtime error message.
11 + */
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: () => {},
14 + params: [],
15 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/reassigned-fnexpr-variable.expect.md new
+86
@@ -0,0 +1,86 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @gating
6 +import * as React from 'react';
7 +
8 +/**
9 + * Test that the correct `Foo` is printed
10 + */
11 +let Foo = () => <div>hello world 1!</div>;
12 +const MemoOne = React.memo(Foo);
13 +Foo = () => <div>hello world 2!</div>;
14 +const MemoTwo = React.memo(Foo);
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: () => {
18 + 'use no memo';
19 + return (
20 + <>
21 + <MemoOne />
22 + <MemoTwo />
23 + </>
24 + );
25 + },
26 + params: [],
27 +};
28 +
29 +```
30 +
31 +## Code
32 +
33 +```javascript
34 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
35 +import { c as _c } from "react/compiler-runtime"; // @gating
36 +import * as React from "react";
37 +
38 +/**
39 + * Test that the correct `Foo` is printed
40 + */
41 +let Foo = isForgetEnabled_Fixtures()
42 + ? () => {
43 + const $ = _c(1);
44 + let t0;
45 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
46 + t0 = <div>hello world 1!</div>;
47 + $[0] = t0;
48 + } else {
49 + t0 = $[0];
50 + }
51 + return t0;
52 + }
53 + : () => <div>hello world 1!</div>;
54 +const MemoOne = React.memo(Foo);
55 +Foo = isForgetEnabled_Fixtures()
56 + ? () => {
57 + const $ = _c(1);
58 + let t0;
59 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
60 + t0 = <div>hello world 2!</div>;
61 + $[0] = t0;
62 + } else {
63 + t0 = $[0];
64 + }
65 + return t0;
66 + }
67 + : () => <div>hello world 2!</div>;
68 +const MemoTwo = React.memo(Foo);
69 +
70 +export const FIXTURE_ENTRYPOINT = {
71 + fn: () => {
72 + "use no memo";
73 + return (
74 + <>
75 + <MemoOne />
76 + <MemoTwo />
77 + </>
78 + );
79 + },
80 + params: [],
81 +};
82 +
83 +```
84 +
85 +### Eval output
86 +(kind: ok) <div>hello world 1!</div><div>hello world 2!</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/reassigned-fnexpr-variable.js new
+23
@@ -0,0 +1,23 @@
1 +// @gating
2 +import * as React from 'react';
3 +
4 +/**
5 + * Test that the correct `Foo` is printed
6 + */
7 +let Foo = () => <div>hello world 1!</div>;
8 +const MemoOne = React.memo(Foo);
9 +Foo = () => <div>hello world 2!</div>;
10 +const MemoTwo = React.memo(Foo);
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: () => {
14 + 'use no memo';
15 + return (
16 + <>
17 + <MemoOne />
18 + <MemoTwo />
19 + </>
20 + );
21 + },
22 + params: [],
23 +};