@samitouri / QOS-React / commits / 858633f900

[compiler] Lower JSXMemberExpression with LoadLocal (#31201)

`JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31201). * #31202 * #31203 * __->__ #31201 * #31200 * #31521

mofeiZ committed Nov 15, 2024 at 13:06 UTC 858633f900768e63870a0a8d450ad7b5d6df0256
14 files changed +299 -7
compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
+7 -1
@@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression(
3186 loc: object.node.loc ?? null,
3187 suggestions: null,
3188 });
3189 - objectPlace = lowerIdentifier(builder, object);
3189 +
3190 + const kind = getLoadKind(builder, object);
3191 + objectPlace = lowerValueToTemporary(builder, {
3192 + kind: kind,
3193 + place: lowerIdentifier(builder, object),
3194 + loc: exprPath.node.loc ?? GeneratedSource,
3195 + });
3196 }
3197 const property = exprPath.get('property').node.name;
3198 return lowerValueToTemporary(builder, {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new
+75
@@ -0,0 +1,75 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {Throw} from 'shared-runtime';
6 +
7 +/**
8 + * Note: this is disabled in the evaluator due to different devmode errors.
9 + * Found differences in evaluator results
10 + * Non-forget (expected):
11 + * (kind: ok) <invalidtag val="[object Object]"></invalidtag>
12 + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
13 + *
14 + * Forget:
15 + * (kind: ok) <invalidtag val="[object Object]"></invalidtag>
16 + * logs: [
17 + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
18 + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
19 + * ]
20 + */
21 +function useFoo() {
22 + const invalidTag = Throw;
23 + /**
24 + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render
25 + * Throw). Note that the jsx transform turns this into a string tag:
26 + * `jsx("invalidTag"...
27 + */
28 + return <invalidTag val={{val: 2}} />;
29 +}
30 +export const FIXTURE_ENTRYPOINT = {
31 + fn: useFoo,
32 + params: [],
33 +};
34 +
35 +```
36 +
37 +## Code
38 +
39 +```javascript
40 +import { c as _c } from "react/compiler-runtime";
41 +import { Throw } from "shared-runtime";
42 +
43 +/**
44 + * Note: this is disabled in the evaluator due to different devmode errors.
45 + * Found differences in evaluator results
46 + * Non-forget (expected):
47 + * (kind: ok) <invalidtag val="[object Object]"></invalidtag>
48 + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
49 + *
50 + * Forget:
51 + * (kind: ok) <invalidtag val="[object Object]"></invalidtag>
52 + * logs: [
53 + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
54 + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
55 + * ]
56 + */
57 +function useFoo() {
58 + const $ = _c(1);
59 + let t0;
60 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
61 + t0 = <invalidTag val={{ val: 2 }} />;
62 + $[0] = t0;
63 + } else {
64 + t0 = $[0];
65 + }
66 + return t0;
67 +}
68 +
69 +export const FIXTURE_ENTRYPOINT = {
70 + fn: useFoo,
71 + params: [],
72 +};
73 +
74 +```
75 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new
+29
@@ -0,0 +1,29 @@
1 +import {Throw} from 'shared-runtime';
2 +
3 +/**
4 + * Note: this is disabled in the evaluator due to different devmode errors.
5 + * Found differences in evaluator results
6 + * Non-forget (expected):
7 + * (kind: ok) <invalidtag val="[object Object]"></invalidtag>
8 + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
9 + *
10 + * Forget:
11 + * (kind: ok) <invalidtag val="[object Object]"></invalidtag>
12 + * logs: [
13 + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
14 + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
15 + * ]
16 + */
17 +function useFoo() {
18 + const invalidTag = Throw;
19 + /**
20 + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render
21 + * Throw). Note that the jsx transform turns this into a string tag:
22 + * `jsx("invalidTag"...
23 + */
24 + return <invalidTag val={{val: 2}} />;
25 +}
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: useFoo,
28 + params: [],
29 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md
+1 -2
@@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime";
27 function useFoo(t0) {
28 const $ = _c(1);
29 const { cond } = t0;
30 - const MyLocal = SharedRuntime;
30 if (cond) {
31 let t1;
32 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
34 - t1 = <MyLocal.Text value={4} />;
33 + t1 = <SharedRuntime.Text value={4} />;
34 $[0] = t1;
35 } else {
36 t1 = $[0];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md
+1 -2
@@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime";
22 import * as SharedRuntime from "shared-runtime";
23 function useFoo() {
24 const $ = _c(1);
25 - const MyLocal = SharedRuntime;
25 let t0;
26 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
28 - t0 = <MyLocal.Text value={4} />;
27 + t0 = <SharedRuntime.Text value={4} />;
28 $[0] = t0;
29 } else {
30 t0 = $[0];
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new
+59
@@ -0,0 +1,59 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import * as SharedRuntime from 'shared-runtime';
6 +import {invoke} from 'shared-runtime';
7 +function useComponentFactory({name}) {
8 + const localVar = SharedRuntime;
9 + const cb = () => <localVar.Stringify>hello world {name}</localVar.Stringify>;
10 + return invoke(cb);
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: useComponentFactory,
15 + params: [{name: 'sathya'}],
16 +};
17 +
18 +```
19 +
20 +## Code
21 +
22 +```javascript
23 +import { c as _c } from "react/compiler-runtime";
24 +import * as SharedRuntime from "shared-runtime";
25 +import { invoke } from "shared-runtime";
26 +function useComponentFactory(t0) {
27 + const $ = _c(4);
28 + const { name } = t0;
29 + let t1;
30 + if ($[0] !== name) {
31 + t1 = () => (
32 + <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>
33 + );
34 + $[0] = name;
35 + $[1] = t1;
36 + } else {
37 + t1 = $[1];
38 + }
39 + const cb = t1;
40 + let t2;
41 + if ($[2] !== cb) {
42 + t2 = invoke(cb);
43 + $[2] = cb;
44 + $[3] = t2;
45 + } else {
46 + t2 = $[3];
47 + }
48 + return t2;
49 +}
50 +
51 +export const FIXTURE_ENTRYPOINT = {
52 + fn: useComponentFactory,
53 + params: [{ name: "sathya" }],
54 +};
55 +
56 +```
57 +
58 +### Eval output
59 +(kind: ok) <div>{"children":["hello world ","sathya"]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new
+12
@@ -0,0 +1,12 @@
1 +import * as SharedRuntime from 'shared-runtime';
2 +import {invoke} from 'shared-runtime';
3 +function useComponentFactory({name}) {
4 + const localVar = SharedRuntime;
5 + const cb = () => <localVar.Stringify>hello world {name}</localVar.Stringify>;
6 + return invoke(cb);
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: useComponentFactory,
11 + params: [{name: 'sathya'}],
12 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new
+45
@@ -0,0 +1,45 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import * as SharedRuntime from 'shared-runtime';
6 +function Component({name}) {
7 + const localVar = SharedRuntime;
8 + return <localVar.Stringify>hello world {name}</localVar.Stringify>;
9 +}
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: Component,
13 + params: [{name: 'sathya'}],
14 +};
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +import { c as _c } from "react/compiler-runtime";
22 +import * as SharedRuntime from "shared-runtime";
23 +function Component(t0) {
24 + const $ = _c(2);
25 + const { name } = t0;
26 + let t1;
27 + if ($[0] !== name) {
28 + t1 = <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
29 + $[0] = name;
30 + $[1] = t1;
31 + } else {
32 + t1 = $[1];
33 + }
34 + return t1;
35 +}
36 +
37 +export const FIXTURE_ENTRYPOINT = {
38 + fn: Component,
39 + params: [{ name: "sathya" }],
40 +};
41 +
42 +```
43 +
44 +### Eval output
45 +(kind: ok) <div>{"children":["hello world ","sathya"]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new
+10
@@ -0,0 +1,10 @@
1 +import * as SharedRuntime from 'shared-runtime';
2 +function Component({name}) {
3 + const localVar = SharedRuntime;
4 + return <localVar.Stringify>hello world {name}</localVar.Stringify>;
5 +}
6 +
7 +export const FIXTURE_ENTRYPOINT = {
8 + fn: Component,
9 + params: [{name: 'sathya'}],
10 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new
+44
@@ -0,0 +1,44 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import * as SharedRuntime from 'shared-runtime';
6 +function Component({name}) {
7 + return <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: Component,
12 + params: [{name: 'sathya'}],
13 +};
14 +
15 +```
16 +
17 +## Code
18 +
19 +```javascript
20 +import { c as _c } from "react/compiler-runtime";
21 +import * as SharedRuntime from "shared-runtime";
22 +function Component(t0) {
23 + const $ = _c(2);
24 + const { name } = t0;
25 + let t1;
26 + if ($[0] !== name) {
27 + t1 = <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
28 + $[0] = name;
29 + $[1] = t1;
30 + } else {
31 + t1 = $[1];
32 + }
33 + return t1;
34 +}
35 +
36 +export const FIXTURE_ENTRYPOINT = {
37 + fn: Component,
38 + params: [{ name: "sathya" }],
39 +};
40 +
41 +```
42 +
43 +### Eval output
44 +(kind: ok) <div>{"children":["hello world ","sathya"]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new
+9
@@ -0,0 +1,9 @@
1 +import * as SharedRuntime from 'shared-runtime';
2 +function Component({name}) {
3 + return <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
4 +}
5 +
6 +export const FIXTURE_ENTRYPOINT = {
7 + fn: Component,
8 + params: [{name: 'sathya'}],
9 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md
+1 -2
@@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime";
25 import * as SharedRuntime from "shared-runtime";
26 function useFoo() {
27 const $ = _c(1);
28 - const MyLocal = SharedRuntime;
28 let t0;
29 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
31 - const callback = () => <MyLocal.Text value={4} />;
30 + const callback = () => <SharedRuntime.Text value={4} />;
31
32 t0 = callback();
33 $[0] = t0;
compiler/packages/snap/src/SproutTodoFilter.ts
+3
@@ -475,6 +475,9 @@ const skipFilter = new Set([
475 'rules-of-hooks/rules-of-hooks-93dc5d5e538a',
476 'rules-of-hooks/rules-of-hooks-69521d94fa03',
477
478 + // false positives
479 + 'invalid-jsx-lowercase-localvar',
480 +
481 // bugs
482 'fbt/bug-fbt-plural-multiple-function-calls',
483 'fbt/bug-fbt-plural-multiple-mixed-call-tag',
compiler/packages/snap/src/sprout/shared-runtime.ts
+3
@@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement {
252 toJSON(props, props?.shouldInvokeFns),
253 );
254 }
255 +export function Throw() {
256 + throw new Error();
257 +}
258
259 export function ValidateMemoization({
260 inputs,