[compiler] Prune all unused array destructure items during DCE

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.

ghstack-source-id: f13c5cc6df
Pull Request resolved: https://github.com/facebook/react/pull/31603
This commit is contained in:
Joe Savona
2024-11-21 00:07:29 -05:00
parent c11c9510fa
commit 8aae2dbba8
12 changed files with 27 additions and 31 deletions
@@ -6,7 +6,6 @@
*/
import {
ArrayPattern,
BlockId,
HIRFunction,
Identifier,
@@ -184,29 +183,28 @@ function rewriteInstruction(instr: Instruction, state: State): void {
switch (instr.value.lvalue.pattern.kind) {
case 'ArrayPattern': {
/*
* For arrays, we can only eliminate unused items from the end of the array,
* so we iterate from the end and break once we find a used item. Note that
* we already know at least one item is used, from the pruneableValue check.
* For arrays, we can prune items prior to the end by replacing
* them with a hole. Items at the end can simply be dropped.
*/
let nextItems: ArrayPattern['items'] | null = null;
const originalItems = instr.value.lvalue.pattern.items;
for (let i = originalItems.length - 1; i >= 0; i--) {
const item = originalItems[i];
let lastEntryIndex = 0;
const items = instr.value.lvalue.pattern.items;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.kind === 'Identifier') {
if (state.isIdOrNameUsed(item.identifier)) {
nextItems = originalItems.slice(0, i + 1);
break;
if (!state.isIdOrNameUsed(item.identifier)) {
items[i] = {kind: 'Hole'};
} else {
lastEntryIndex = i;
}
} else if (item.kind === 'Spread') {
if (state.isIdOrNameUsed(item.place.identifier)) {
nextItems = originalItems.slice(0, i + 1);
break;
if (!state.isIdOrNameUsed(item.place.identifier)) {
items[i] = {kind: 'Hole'};
} else {
lastEntryIndex = i;
}
}
}
if (nextItems !== null) {
instr.value.lvalue.pattern.items = nextItems;
}
items.length = lastEntryIndex + 1;
break;
}
case 'ObjectPattern': {
@@ -16,7 +16,7 @@ function component() {
import { c as _c } from "react/compiler-runtime";
function component() {
const $ = _c(1);
const [x, setX] = useState(0);
const [, setX] = useState(0);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const handler = (v) => setX(v);
@@ -35,8 +35,7 @@ function Component(props) {
}
let d;
if ($[2] !== props.c) {
const [c, ...t0] = props.c;
d = t0;
[, ...d] = props.c;
$[2] = props.c;
$[3] = d;
} else {
@@ -24,7 +24,7 @@ function Component(props) {
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(2);
const [value, setValue] = useState(null);
const [, setValue] = useState(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (e) => setValue((value_0) => value_0 + e.target.value);
@@ -39,7 +39,7 @@ import { useState } from "react";
function Component(props) {
const $ = _c(1);
const [_state, setState] = useState();
const [, setState] = useState();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const a = () => b();
@@ -28,7 +28,7 @@ import { useCallback, useTransition } from "react";
function useFoo() {
const $ = _c(1);
const [t, start] = useTransition();
const [, start] = useTransition();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
@@ -32,7 +32,7 @@ function Component(props) {
const $ = _c(5);
React.useContext(FooContext);
const ref = React.useRef();
const [x, setX] = React.useState(false);
const [, setX] = React.useState(false);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
@@ -61,8 +61,8 @@ import { useState } from "react";
function Component(props) {
const $ = _c(5);
const [x, setX] = useState(false);
const [y, setY] = useState(false);
const [, setX] = useState(false);
const [, setY] = useState(false);
let setState;
if (props.cond) {
setState = setX;
@@ -52,8 +52,7 @@ function Component(props) {
const { buttons } = props;
let nonPrimaryButtons;
if ($[0] !== buttons) {
const [primaryButton, ...t0] = buttons;
nonPrimaryButtons = t0;
[, ...nonPrimaryButtons] = buttons;
$[0] = buttons;
$[1] = nonPrimaryButtons;
} else {
@@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
function foo(props) {
const [x, unused, y] = props.a;
const [x, , y] = props.a;
return x + y;
}
@@ -29,7 +29,7 @@ import { useActionState } from "react";
function Component() {
const $ = _c(1);
const [actionState, dispatchAction] = useActionState();
const [, dispatchAction] = useActionState();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const onSubmitAction = () => {
@@ -30,7 +30,7 @@ import { useReducer } from "react";
function f() {
const $ = _c(1);
const [state, dispatch] = useReducer();
const [, dispatch] = useReducer();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const onClick = () => {