@samitouri / QOS-React / commits / dbc5f36fe9

[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 +};