[compiler] Support method-call version of macro functions
Adds fixtures for `macro.namespace(...)` style invocations which we use internally in some cases instead of just `macro(...)`. I tried every example i could think of that could possibly break it (including basing one off of another fixture where we hit an invariant related due to a temporary being emitted for a method call), and they all worked. I just had to fix an existing bug where we early return in some cases instead of continuing, which is a holdover from when this pass was originally written as a ReactiveFunction visitor. ghstack-source-id: c01f45b3ef6f42b6d1f1ff0508aea258000e0fce Pull Request resolved: https://github.com/facebook/react/pull/29899
Joe Savona committed
Jun 14, 2024 at 08:44 UTC
dbc5f36fe985258040976056889d8d27b9f67b8d
6 files changed
+233
-5
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+1
-1
@@ -254,7 +254,7 @@ function* runWithEnvironment(
254
memoizeFbtOperandsInSameScope(hir);
255
yield log({
256
kind: "hir",
257
- name: "MemoizeFbtOperandsInSameScope",
257
+ name: "MemoizeFbtAndMacroOperandsInSameScope",
258
value: hir,
259
});
260
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MemoizeFbtAndMacroOperandsInSameScope.ts
+3
-4
@@ -15,7 +15,6 @@ import {
15
import { eachReactiveValueOperand } from "./visitors";
16
17
/**
18
- * This pass supports the
18
* This pass supports the `fbt` translation system (https://facebook.github.io/fbt/)
19
* as well as similar user-configurable macro-like APIs where it's important that
20
* the name of the function not be changed, and it's literal arguments not be turned
@@ -75,7 +74,7 @@ function visit(
74
for (const instruction of block.instructions) {
75
const { lvalue, value } = instruction;
76
if (lvalue === null) {
78
- return;
77
+ continue;
78
}
79
if (
80
value.kind === "Primitive" &&
@@ -96,7 +95,7 @@ function visit(
95
} else if (isFbtCallExpression(fbtValues, value)) {
96
const fbtScope = lvalue.identifier.scope;
97
if (fbtScope === null) {
99
- return;
98
+ continue;
99
}
100
101
/*
@@ -122,7 +121,7 @@ function visit(
121
) {
122
const fbtScope = lvalue.identifier.scope;
123
if (fbtScope === null) {
125
- return;
124
+ continue;
125
}
126
127
/*
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md
new
+100
@@ -0,0 +1,100 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx)
6
+import { identity } from "shared-runtime";
7
+
8
+const DARK = "dark";
9
+
10
+function Component() {
11
+ const theme = useTheme();
12
+ return (
13
+ <div
14
+ className={cx.foo({
15
+ "styles/light": true,
16
+ "styles/dark": identity([theme.getTheme()]),
17
+ })}
18
+ />
19
+ );
20
+}
21
+
22
+function cx(obj) {
23
+ const classes = [];
24
+ for (const [key, value] of Object.entries(obj)) {
25
+ if (value) {
26
+ classes.push(key);
27
+ }
28
+ }
29
+ return classes.join(" ");
30
+}
31
+
32
+function useTheme() {
33
+ return {
34
+ getTheme() {
35
+ return DARK;
36
+ },
37
+ };
38
+}
39
+
40
+export const FIXTURE_ENTRYPOINT = {
41
+ fn: Component,
42
+ params: [{}],
43
+};
44
+
45
+```
46
+
47
+## Code
48
+
49
+```javascript
50
+import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx)
51
+import { identity } from "shared-runtime";
52
+
53
+const DARK = "dark";
54
+
55
+function Component() {
56
+ const $ = _c(2);
57
+ const theme = useTheme();
58
+
59
+ const t0 = cx.foo({
60
+ "styles/light": true,
61
+ "styles/dark": identity([theme.getTheme()]),
62
+ });
63
+ let t1;
64
+ if ($[0] !== t0) {
65
+ t1 = <div className={t0} />;
66
+ $[0] = t0;
67
+ $[1] = t1;
68
+ } else {
69
+ t1 = $[1];
70
+ }
71
+ return t1;
72
+}
73
+
74
+function cx(obj) {
75
+ const classes = [];
76
+ for (const [key, value] of Object.entries(obj)) {
77
+ if (value) {
78
+ classes.push(key);
79
+ }
80
+ }
81
+ return classes.join(" ");
82
+}
83
+
84
+function useTheme() {
85
+ return {
86
+ getTheme() {
87
+ return DARK;
88
+ },
89
+ };
90
+}
91
+
92
+export const FIXTURE_ENTRYPOINT = {
93
+ fn: Component,
94
+ params: [{}],
95
+};
96
+
97
+```
98
+
99
+### Eval output
100
+(kind: exception) cx.foo is not a function
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js
new
+39
@@ -0,0 +1,39 @@
1
+// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx)
2
+import { identity } from "shared-runtime";
3
+
4
+const DARK = "dark";
5
+
6
+function Component() {
7
+ const theme = useTheme();
8
+ return (
9
+ <div
10
+ className={cx.foo({
11
+ "styles/light": true,
12
+ "styles/dark": identity([theme.getTheme()]),
13
+ })}
14
+ />
15
+ );
16
+}
17
+
18
+function cx(obj) {
19
+ const classes = [];
20
+ for (const [key, value] of Object.entries(obj)) {
21
+ if (value) {
22
+ classes.push(key);
23
+ }
24
+ }
25
+ return classes.join(" ");
26
+}
27
+
28
+function useTheme() {
29
+ return {
30
+ getTheme() {
31
+ return DARK;
32
+ },
33
+ };
34
+}
35
+
36
+export const FIXTURE_ENTRYPOINT = {
37
+ fn: Component,
38
+ params: [{}],
39
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.expect.md
new
+68
@@ -0,0 +1,68 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import { makeArray } from "shared-runtime";
6
+
7
+function Component() {
8
+ const items = makeArray("foo", "bar", "", null, "baz", false, "merp");
9
+ const classname = cx.namespace(...items.filter(isNonEmptyString));
10
+ return <div className={classname}>Ok</div>;
11
+}
12
+
13
+function isNonEmptyString(s) {
14
+ return typeof s === "string" && s.trim().length !== 0;
15
+}
16
+
17
+const cx = {
18
+ namespace(...items) {
19
+ return items.join(" ");
20
+ },
21
+};
22
+
23
+export const FIXTURE_ENTRYPOINT = {
24
+ fn: Component,
25
+ params: [{}],
26
+};
27
+
28
+```
29
+
30
+## Code
31
+
32
+```javascript
33
+import { c as _c } from "react/compiler-runtime";
34
+import { makeArray } from "shared-runtime";
35
+
36
+function Component() {
37
+ const $ = _c(1);
38
+ let t0;
39
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
40
+ const items = makeArray("foo", "bar", "", null, "baz", false, "merp");
41
+ const classname = cx.namespace(...items.filter(isNonEmptyString));
42
+ t0 = <div className={classname}>Ok</div>;
43
+ $[0] = t0;
44
+ } else {
45
+ t0 = $[0];
46
+ }
47
+ return t0;
48
+}
49
+
50
+function isNonEmptyString(s) {
51
+ return typeof s === "string" && s.trim().length !== 0;
52
+}
53
+
54
+const cx = {
55
+ namespace(...items) {
56
+ return items.join(" ");
57
+ },
58
+};
59
+
60
+export const FIXTURE_ENTRYPOINT = {
61
+ fn: Component,
62
+ params: [{}],
63
+};
64
+
65
+```
66
+
67
+### Eval output
68
+(kind: ok) <div class="foo bar baz merp">Ok</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.js
new
+22
@@ -0,0 +1,22 @@
1
+import { makeArray } from "shared-runtime";
2
+
3
+function Component() {
4
+ const items = makeArray("foo", "bar", "", null, "baz", false, "merp");
5
+ const classname = cx.namespace(...items.filter(isNonEmptyString));
6
+ return <div className={classname}>Ok</div>;
7
+}
8
+
9
+function isNonEmptyString(s) {
10
+ return typeof s === "string" && s.trim().length !== 0;
11
+}
12
+
13
+const cx = {
14
+ namespace(...items) {
15
+ return items.join(" ");
16
+ },
17
+};
18
+
19
+export const FIXTURE_ENTRYPOINT = {
20
+ fn: Component,
21
+ params: [{}],
22
+};