[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
+};