[compiler] Make inserting outlined functions more resilient
To handle more cases, always append the synthetic outlined function as a new child of the module rather than make assumptions about the original function. This should handle whatever case where the original function expression may be a child of a variety of parents ghstack-source-id: 8581edb8be706c0f4ce506caf7cd9b0f5a145bc8 Pull Request resolved: https://github.com/facebook/react/pull/30466
Lauren Tan committed
Jul 25, 2024 at 15:18 UTC
9f3bbb05ab29d531e343ddb2e85d89943669b6e7
5 files changed
+100
-76
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts
+7
-11
@@ -196,6 +196,7 @@ export function createNewFunctionNode(
196
}
197
198
function insertNewOutlinedFunctionNode(
199
+ program: NodePath<t.Program>,
200
originalFn: BabelFn,
201
compiledFn: CodegenFunction,
202
): NodePath<t.Function> {
@@ -216,14 +217,6 @@ function insertNewOutlinedFunctionNode(
217
*/
218
case 'ArrowFunctionExpression':
219
case 'FunctionExpression': {
219
- CompilerError.invariant(
220
- originalFn.parentPath.isVariableDeclarator() &&
221
- originalFn.parentPath.parentPath.isVariableDeclaration(),
222
- {
223
- reason: 'Expected a variable declarator parent',
224
- loc: originalFn.node.loc ?? null,
225
- },
226
- );
220
const fn: t.FunctionDeclaration = {
221
type: 'FunctionDeclaration',
222
id: compiledFn.id,
@@ -233,8 +226,7 @@ function insertNewOutlinedFunctionNode(
226
params: compiledFn.params,
227
body: compiledFn.body,
228
};
236
- const varDecl = originalFn.parentPath.parentPath;
237
- const insertedFuncDecl = varDecl.insertAfter(fn)[0]!;
229
+ const insertedFuncDecl = program.pushContainer('body', [fn])[0]!;
230
CompilerError.invariant(insertedFuncDecl.isFunctionDeclaration(), {
231
reason: 'Expected inserted function declaration',
232
description: `Got: ${insertedFuncDecl}`,
@@ -468,7 +460,11 @@ export function compileProgram(
460
reason: 'Unexpected nested outlined functions',
461
loc: outlined.fn.loc,
462
});
471
- const fn = insertNewOutlinedFunctionNode(current.fn, outlined.fn);
463
+ const fn = insertNewOutlinedFunctionNode(
464
+ program,
465
+ current.fn,
466
+ outlined.fn,
467
+ );
468
fn.skip();
469
ALREADY_COMPILED.add(fn.node);
470
if (outlined.type !== null) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-outlining-in-react-memo.expect.md
deleted
-62
@@ -1,62 +0,0 @@
1
-
2
-## Input
3
-
4
-```javascript
5
-function Component(props) {
6
- return <View {...props} />;
7
-}
8
-
9
-const View = React.memo(({items}) => {
10
- return (
11
- <ul>
12
- {items.map(item => (
13
- <li key={item.id}>{item.name}</li>
14
- ))}
15
- </ul>
16
- );
17
-});
18
-
19
-export const FIXTURE_ENTRYPOINT = {
20
- fn: Component,
21
- params: [
22
- {
23
- items: [
24
- {id: 2, name: 'foo'},
25
- {id: 3, name: 'bar'},
26
- ],
27
- },
28
- ],
29
-};
30
-
31
-```
32
-
33
-
34
-## Error
35
-
36
-```
37
- 3 | }
38
- 4 |
39
-> 5 | const View = React.memo(({items}) => {
40
- | ^^^^^^^^^^^^^^
41
-> 6 | return (
42
- | ^^^^^^^^^^
43
-> 7 | <ul>
44
- | ^^^^^^^^^^
45
-> 8 | {items.map(item => (
46
- | ^^^^^^^^^^
47
-> 9 | <li key={item.id}>{item.name}</li>
48
- | ^^^^^^^^^^
49
-> 10 | ))}
50
- | ^^^^^^^^^^
51
-> 11 | </ul>
52
- | ^^^^^^^^^^
53
-> 12 | );
54
- | ^^^^^^^^^^
55
-> 13 | });
56
- | ^^ Invariant: Expected a variable declarator parent (5:13)
57
- 14 |
58
- 15 | export const FIXTURE_ENTRYPOINT = {
59
- 16 | fn: Component,
60
-```
61
-
62
-
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlining-in-func-expr.expect.md
+3
-3
@@ -50,9 +50,6 @@ const Component2 = (props) => {
50
}
51
return t1;
52
};
53
-function _temp(item) {
54
- return <li key={item.id}>{item.name}</li>;
55
-}
53
54
export const FIXTURE_ENTRYPOINT = {
55
fn: Component2,
@@ -65,6 +62,9 @@ export const FIXTURE_ENTRYPOINT = {
62
},
63
],
64
};
65
+function _temp(item) {
66
+ return <li key={item.id}>{item.name}</li>;
67
+}
68
69
```
70
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlining-in-react-memo.expect.md
new
+90
@@ -0,0 +1,90 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ return <View {...props} />;
7
+}
8
+
9
+const View = React.memo(({items}) => {
10
+ return (
11
+ <ul>
12
+ {items.map(item => (
13
+ <li key={item.id}>{item.name}</li>
14
+ ))}
15
+ </ul>
16
+ );
17
+});
18
+
19
+export const FIXTURE_ENTRYPOINT = {
20
+ fn: Component,
21
+ params: [
22
+ {
23
+ items: [
24
+ {id: 2, name: 'foo'},
25
+ {id: 3, name: 'bar'},
26
+ ],
27
+ },
28
+ ],
29
+};
30
+
31
+```
32
+
33
+## Code
34
+
35
+```javascript
36
+import { c as _c } from "react/compiler-runtime";
37
+function Component(props) {
38
+ const $ = _c(2);
39
+ let t0;
40
+ if ($[0] !== props) {
41
+ t0 = <View {...props} />;
42
+ $[0] = props;
43
+ $[1] = t0;
44
+ } else {
45
+ t0 = $[1];
46
+ }
47
+ return t0;
48
+}
49
+
50
+const View = React.memo((t0) => {
51
+ const $ = _c(4);
52
+ const { items } = t0;
53
+ let t1;
54
+ if ($[0] !== items) {
55
+ t1 = items.map(_temp);
56
+ $[0] = items;
57
+ $[1] = t1;
58
+ } else {
59
+ t1 = $[1];
60
+ }
61
+ let t2;
62
+ if ($[2] !== t1) {
63
+ t2 = <ul>{t1}</ul>;
64
+ $[2] = t1;
65
+ $[3] = t2;
66
+ } else {
67
+ t2 = $[3];
68
+ }
69
+ return t2;
70
+});
71
+
72
+export const FIXTURE_ENTRYPOINT = {
73
+ fn: Component,
74
+ params: [
75
+ {
76
+ items: [
77
+ { id: 2, name: "foo" },
78
+ { id: 3, name: "bar" },
79
+ ],
80
+ },
81
+ ],
82
+};
83
+function _temp(item) {
84
+ return <li key={item.id}>{item.name}</li>;
85
+}
86
+
87
+```
88
+
89
+### Eval output
90
+(kind: ok) <ul><li>foo</li><li>bar</li></ul>
\ No newline at end of file