@samitouri / QOS-React-1 / commits / e096403c59

[compiler] Infer types for properties after holes in array patterns (#34847)

In InferTypes when we infer types for properties during destructuring, we were breaking out of the loop when we encounter a hole in the array. Instead we should just skip that element and continue inferring later properties. Closes #34748 --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34847). * #34855 * __->__ #34847

Joseph Savona committed Oct 15, 2025 at 09:45 UTC e096403c595d67b689473908a52979c76bbefb9e
9 files changed +192 -47
compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts
+1 -1
@@ -393,7 +393,7 @@ function* generateInstructionTypes(
393 shapeId: BuiltInArrayId,
394 });
395 } else {
396 - break;
396 + continue;
397 }
398 }
399 } else {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-render-unbound-state.expect.md new
+41
@@ -0,0 +1,41 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + // Intentionally don't bind state, this repros a bug where we didn't
7 + // infer the type of destructured properties after a hole in the array
8 + let [, setState] = useState();
9 + setState(1);
10 + return props.foo;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: ['TodoAdd'],
16 + isComponent: 'TodoAdd',
17 +};
18 +
19 +```
20 +
21 +
22 +## Error
23 +
24 +```
25 +Found 1 error:
26 +
27 +Error: Calling setState during render may trigger an infinite loop
28 +
29 +Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
30 +
31 +error.invalid-setState-in-render-unbound-state.ts:5:2
32 + 3 | // infer the type of destructured properties after a hole in the array
33 + 4 | let [, setState] = useState();
34 +> 5 | setState(1);
35 + | ^^^^^^^^ Found setState() in render
36 + 6 | return props.foo;
37 + 7 | }
38 + 8 |
39 +```
40 +
41 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-render-unbound-state.js new
+13
@@ -0,0 +1,13 @@
1 +function Component(props) {
2 + // Intentionally don't bind state, this repros a bug where we didn't
3 + // infer the type of destructured properties after a hole in the array
4 + let [, setState] = useState();
5 + setState(1);
6 + return props.foo;
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: Component,
11 + params: ['TodoAdd'],
12 + isComponent: 'TodoAdd',
13 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/holey-array.expect.md deleted
-35
@@ -1,35 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -function t(props) {
6 - let [, setstate] = useState();
7 - setstate(1);
8 - return props.foo;
9 -}
10 -
11 -export const FIXTURE_ENTRYPOINT = {
12 - fn: t,
13 - params: ['TodoAdd'],
14 - isComponent: 'TodoAdd',
15 -};
16 -
17 -```
18 -
19 -## Code
20 -
21 -```javascript
22 -function t(props) {
23 - const [, setstate] = useState();
24 - setstate(1);
25 - return props.foo;
26 -}
27 -
28 -export const FIXTURE_ENTRYPOINT = {
29 - fn: t,
30 - params: ["TodoAdd"],
31 - isComponent: "TodoAdd",
32 -};
33 -
34 -```
35 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/holey-array.js deleted
-11
@@ -1,11 +0,0 @@
1 -function t(props) {
2 - let [, setstate] = useState();
3 - setstate(1);
4 - return props.foo;
5 -}
6 -
7 -export const FIXTURE_ENTRYPOINT = {
8 - fn: t,
9 - params: ['TodoAdd'],
10 - isComponent: 'TodoAdd',
11 -};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-memo-transition-no-ispending.expect.md new
+52
@@ -0,0 +1,52 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +import {useCallback, useTransition} from 'react';
7 +
8 +function useFoo() {
9 + const [, /* isPending intentionally not captured */ start] = useTransition();
10 +
11 + return useCallback(() => {
12 + start();
13 + }, []);
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: useFoo,
18 + params: [],
19 +};
20 +
21 +```
22 +
23 +## Code
24 +
25 +```javascript
26 +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
27 +import { useCallback, useTransition } from "react";
28 +
29 +function useFoo() {
30 + const $ = _c(1);
31 + const [, start] = useTransition();
32 + let t0;
33 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
34 + t0 = () => {
35 + start();
36 + };
37 + $[0] = t0;
38 + } else {
39 + t0 = $[0];
40 + }
41 + return t0;
42 +}
43 +
44 +export const FIXTURE_ENTRYPOINT = {
45 + fn: useFoo,
46 + params: [],
47 +};
48 +
49 +```
50 +
51 +### Eval output
52 +(kind: ok) "[[ function params=0 ]]"
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-memo-transition-no-ispending.js new
+15
@@ -0,0 +1,15 @@
1 +// @validatePreserveExistingMemoizationGuarantees
2 +import {useCallback, useTransition} from 'react';
3 +
4 +function useFoo() {
5 + const [, /* isPending intentionally not captured */ start] = useTransition();
6 +
7 + return useCallback(() => {
8 + start();
9 + }, []);
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: useFoo,
14 + params: [],
15 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-memo-unused-state.expect.md new
+55
@@ -0,0 +1,55 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +import {useCallback, useTransition} from 'react';
7 +
8 +function useFoo() {
9 + const [, /* state value intentionally not captured */ setState] = useState();
10 +
11 + return useCallback(() => {
12 + setState(x => x + 1);
13 + }, []);
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: useFoo,
18 + params: [],
19 +};
20 +
21 +```
22 +
23 +## Code
24 +
25 +```javascript
26 +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
27 +import { useCallback, useTransition } from "react";
28 +
29 +function useFoo() {
30 + const $ = _c(1);
31 + const [, setState] = useState();
32 + let t0;
33 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
34 + t0 = () => {
35 + setState(_temp);
36 + };
37 + $[0] = t0;
38 + } else {
39 + t0 = $[0];
40 + }
41 + return t0;
42 +}
43 +function _temp(x) {
44 + return x + 1;
45 +}
46 +
47 +export const FIXTURE_ENTRYPOINT = {
48 + fn: useFoo,
49 + params: [],
50 +};
51 +
52 +```
53 +
54 +### Eval output
55 +(kind: exception) useState is not defined
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-memo-unused-state.js new
+15
@@ -0,0 +1,15 @@
1 +// @validatePreserveExistingMemoizationGuarantees
2 +import {useCallback, useTransition} from 'react';
3 +
4 +function useFoo() {
5 + const [, /* state value intentionally not captured */ setState] = useState();
6 +
7 + return useCallback(() => {
8 + setState(x => x + 1);
9 + }, []);
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: useFoo,
14 + params: [],
15 +};