[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
This commit is contained in:
Joseph Savona
2024-11-22 15:59:59 -05:00
committed by GitHub
parent aba370f1e4
commit eee5ca2a92
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 = () => {