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

[compiler] Prune all unused array destructure items during DCE (#31619)

We didn't originally support holes within array patterns, so DCE was only able to prune unused items from the end of an array pattern. Now that we support holes we can replace any unused item with a hole, and then just prune the items to the last identifier/spread entry. Note: this was motivated by finding useState where either the state or setState go unused — both are strong indications that you're violating the rules in some way. By DCE-ing the unused portions of the useState destructuring we can easily check if you're ignoring either value. closes #31603 This is a redo of that PR not using ghstack

Joseph Savona committed Nov 22, 2024 at 15:59 UTC eee5ca2a92c32103b6bc3c1f7e6cdf1c4807ee56
12 files changed +27 -31
compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts
+15 -17
@@ -6,7 +6,6 @@
6 */
7
8 import {
9 - ArrayPattern,
9 BlockId,
10 HIRFunction,
11 Identifier,
@@ -184,29 +183,28 @@ function rewriteInstruction(instr: Instruction, state: State): void {
183 switch (instr.value.lvalue.pattern.kind) {
184 case 'ArrayPattern': {
185 /*
187 - * For arrays, we can only eliminate unused items from the end of the array,
188 - * so we iterate from the end and break once we find a used item. Note that
189 - * we already know at least one item is used, from the pruneableValue check.
186 + * For arrays, we can prune items prior to the end by replacing
187 + * them with a hole. Items at the end can simply be dropped.
188 */
191 - let nextItems: ArrayPattern['items'] | null = null;
192 - const originalItems = instr.value.lvalue.pattern.items;
193 - for (let i = originalItems.length - 1; i >= 0; i--) {
194 - const item = originalItems[i];
189 + let lastEntryIndex = 0;
190 + const items = instr.value.lvalue.pattern.items;
191 + for (let i = 0; i < items.length; i++) {
192 + const item = items[i];
193 if (item.kind === 'Identifier') {
196 - if (state.isIdOrNameUsed(item.identifier)) {
197 - nextItems = originalItems.slice(0, i + 1);
198 - break;
194 + if (!state.isIdOrNameUsed(item.identifier)) {
195 + items[i] = {kind: 'Hole'};
196 + } else {
197 + lastEntryIndex = i;
198 }
199 } else if (item.kind === 'Spread') {
201 - if (state.isIdOrNameUsed(item.place.identifier)) {
202 - nextItems = originalItems.slice(0, i + 1);
203 - break;
200 + if (!state.isIdOrNameUsed(item.place.identifier)) {
201 + items[i] = {kind: 'Hole'};
202 + } else {
203 + lastEntryIndex = i;
204 }
205 }
206 }
207 - if (nextItems !== null) {
208 - instr.value.lvalue.pattern.items = nextItems;
209 - }
207 + items.length = lastEntryIndex + 1;
208 break;
209 }
210 case 'ObjectPattern': {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md
+1 -1
@@ -16,7 +16,7 @@ function component() {
16 import { c as _c } from "react/compiler-runtime";
17 function component() {
18 const $ = _c(1);
19 - const [x, setX] = useState(0);
19 + const [, setX] = useState(0);
20 let t0;
21 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
22 const handler = (v) => setX(v);
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md
+1 -2
@@ -35,8 +35,7 @@ function Component(props) {
35 }
36 let d;
37 if ($[2] !== props.c) {
38 - const [c, ...t0] = props.c;
39 - d = t0;
38 + [, ...d] = props.c;
39 $[2] = props.c;
40 $[3] = d;
41 } else {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md
+1 -1
@@ -24,7 +24,7 @@ function Component(props) {
24 import { c as _c } from "react/compiler-runtime";
25 function Component(props) {
26 const $ = _c(2);
27 - const [value, setValue] = useState(null);
27 + const [, setValue] = useState(null);
28 let t0;
29 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30 t0 = (e) => setValue((value_0) => value_0 + e.target.value);
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md
+1 -1
@@ -39,7 +39,7 @@ import { useState } from "react";
39
40 function Component(props) {
41 const $ = _c(1);
42 - const [_state, setState] = useState();
42 + const [, setState] = useState();
43 let t0;
44 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
45 const a = () => b();
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md
+1 -1
@@ -28,7 +28,7 @@ import { useCallback, useTransition } from "react";
28
29 function useFoo() {
30 const $ = _c(1);
31 - const [t, start] = useTransition();
31 + const [, start] = useTransition();
32 let t0;
33 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
34 t0 = () => {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md
+1 -1
@@ -32,7 +32,7 @@ function Component(props) {
32 const $ = _c(5);
33 React.useContext(FooContext);
34 const ref = React.useRef();
35 - const [x, setX] = React.useState(false);
35 + const [, setX] = React.useState(false);
36 let t0;
37 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
38 t0 = () => {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md
+2 -2
@@ -61,8 +61,8 @@ import { useState } from "react";
61
62 function Component(props) {
63 const $ = _c(5);
64 - const [x, setX] = useState(false);
65 - const [y, setY] = useState(false);
64 + const [, setX] = useState(false);
65 + const [, setY] = useState(false);
66 let setState;
67 if (props.cond) {
68 setState = setX;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md
+1 -2
@@ -52,8 +52,7 @@ function Component(props) {
52 const { buttons } = props;
53 let nonPrimaryButtons;
54 if ($[0] !== buttons) {
55 - const [primaryButton, ...t0] = buttons;
56 - nonPrimaryButtons = t0;
55 + [, ...nonPrimaryButtons] = buttons;
56 $[0] = buttons;
57 $[1] = nonPrimaryButtons;
58 } else {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md
+1 -1
@@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = {
19
20 ```javascript
21 function foo(props) {
22 - const [x, unused, y] = props.a;
22 + const [x, , y] = props.a;
23 return x + y;
24 }
25
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md
+1 -1
@@ -29,7 +29,7 @@ import { useActionState } from "react";
29
30 function Component() {
31 const $ = _c(1);
32 - const [actionState, dispatchAction] = useActionState();
32 + const [, dispatchAction] = useActionState();
33 let t0;
34 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
35 const onSubmitAction = () => {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md
+1 -1
@@ -30,7 +30,7 @@ import { useReducer } from "react";
30
31 function f() {
32 const $ = _c(1);
33 - const [state, dispatch] = useReducer();
33 + const [, dispatch] = useReducer();
34 let t0;
35 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
36 const onClick = () => {