@samitouri / QOS-React-2 / commits / fe813143e2

[compiler] Check TSAsExpression and TSNonNullExpression reorderability (#33788)

## Summary The `TSAsExpression` and `TSNonNullExpression` nodes are supported by `lowerExpression()` but `isReorderableExpression()` does not check if they can be reordered. This PR updates `isReorderableExpression()` to handle these two node types by adding cases that fall through to the existing `TypeCastExpression` case. We ran `react-compiler-healthcheck` at scale on several of our repos and found dozens of `` (BuildHIR::node.lowerReorderableExpression) Expression type `TSAsExpression` cannot be safely reordered`` errors and a handful for `TSNonNullExpression`. ## How did you test this change? In this case I added two fixture tests

Henry Q. Dineen committed Jul 15, 2025 at 14:50 UTC fe813143e23675963f92d25fa1fb35b39db40272
5 files changed +150
compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
+2
@@ -3001,6 +3001,8 @@ function isReorderableExpression(
3001 }
3002 }
3003 }
3004 + case 'TSAsExpression':
3005 + case 'TSNonNullExpression':
3006 case 'TypeCastExpression': {
3007 return isReorderableExpression(
3008 builder,
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-as-expression-default-value.expect.md new
+67
@@ -0,0 +1,67 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +type Status = 'pending' | 'success' | 'error';
6 +
7 +const StatusIndicator = ({status}: {status: Status}) => {
8 + return <div className={`status-${status}`}>Status: {status}</div>;
9 +};
10 +
11 +const Component = ({status = 'pending' as Status}) => {
12 + return <StatusIndicator status={status} />;
13 +};
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Component,
17 + params: [{status: 'success'}],
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +import { c as _c } from "react/compiler-runtime";
26 +type Status = "pending" | "success" | "error";
27 +
28 +const StatusIndicator = (t0) => {
29 + const $ = _c(3);
30 + const { status } = t0;
31 + const t1 = `status-${status}`;
32 + let t2;
33 + if ($[0] !== status || $[1] !== t1) {
34 + t2 = <div className={t1}>Status: {status}</div>;
35 + $[0] = status;
36 + $[1] = t1;
37 + $[2] = t2;
38 + } else {
39 + t2 = $[2];
40 + }
41 + return t2;
42 +};
43 +
44 +const Component = (t0) => {
45 + const $ = _c(2);
46 + const { status: t1 } = t0;
47 + const status = t1 === undefined ? ("pending" as Status) : t1;
48 + let t2;
49 + if ($[0] !== status) {
50 + t2 = <StatusIndicator status={status} />;
51 + $[0] = status;
52 + $[1] = t2;
53 + } else {
54 + t2 = $[1];
55 + }
56 + return t2;
57 +};
58 +
59 +export const FIXTURE_ENTRYPOINT = {
60 + fn: Component,
61 + params: [{ status: "success" }],
62 +};
63 +
64 +```
65 +
66 +### Eval output
67 +(kind: ok) <div class="status-success">Status: success</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-as-expression-default-value.tsx new
+14
@@ -0,0 +1,14 @@
1 +type Status = 'pending' | 'success' | 'error';
2 +
3 +const StatusIndicator = ({status}: {status: Status}) => {
4 + return <div className={`status-${status}`}>Status: {status}</div>;
5 +};
6 +
7 +const Component = ({status = 'pending' as Status}) => {
8 + return <StatusIndicator status={status} />;
9 +};
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: Component,
13 + params: [{status: 'success'}],
14 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.expect.md new
+54
@@ -0,0 +1,54 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +const THEME_MAP: ReadonlyMap<string, string> = new Map([
6 + ['default', 'light'],
7 + ['dark', 'dark'],
8 +]);
9 +
10 +export const Component = ({theme = THEME_MAP.get('default')!}) => {
11 + return <div className={`theme-${theme}`}>User preferences</div>;
12 +};
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: Component,
16 + params: [{status: 'success'}],
17 +};
18 +
19 +```
20 +
21 +## Code
22 +
23 +```javascript
24 +import { c as _c } from "react/compiler-runtime";
25 +const THEME_MAP: ReadonlyMap<string, string> = new Map([
26 + ["default", "light"],
27 + ["dark", "dark"],
28 +]);
29 +
30 +export const Component = (t0) => {
31 + const $ = _c(2);
32 + const { theme: t1 } = t0;
33 + const theme = t1 === undefined ? THEME_MAP.get("default") : t1;
34 + const t2 = `theme-${theme}`;
35 + let t3;
36 + if ($[0] !== t2) {
37 + t3 = <div className={t2}>User preferences</div>;
38 + $[0] = t2;
39 + $[1] = t3;
40 + } else {
41 + t3 = $[1];
42 + }
43 + return t3;
44 +};
45 +
46 +export const FIXTURE_ENTRYPOINT = {
47 + fn: Component,
48 + params: [{ status: "success" }],
49 +};
50 +
51 +```
52 +
53 +### Eval output
54 +(kind: ok) <div class="theme-light">User preferences</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.tsx new
+13
@@ -0,0 +1,13 @@
1 +const THEME_MAP: ReadonlyMap<string, string> = new Map([
2 + ['default', 'light'],
3 + ['dark', 'dark'],
4 +]);
5 +
6 +export const Component = ({theme = THEME_MAP.get('default')!}) => {
7 + return <div className={`theme-${theme}`}>User preferences</div>;
8 +};
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: Component,
12 + params: [{status: 'success'}],
13 +};