[compiler] Fix bug with reassigning function param in destructuring (#33624)
Closes #33577, a bug with ExtractScopeDeclarationsFromDestructuring and codegen when a function param is reassigned. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33624). * #33643 * #33642 * #33640 * #33625 * __->__ #33624
Joseph Savona committed
Jun 25, 2025 at 11:10 UTC
9894c488e0d9a4d9759d80ba8666d4d094b894e9
4 files changed
+83
-5
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
+3
-5
@@ -349,11 +349,9 @@ function codegenReactiveFunction(
349
fn: ReactiveFunction,
350
): Result<CodegenFunction, CompilerError> {
351
for (const param of fn.params) {
352
- if (param.kind === 'Identifier') {
353
- cx.temp.set(param.identifier.declarationId, null);
354
- } else {
355
- cx.temp.set(param.place.identifier.declarationId, null);
356
- }
352
+ const place = param.kind === 'Identifier' ? param : param.place;
353
+ cx.temp.set(place.identifier.declarationId, null);
354
+ cx.declare(place.identifier);
355
}
356
357
const params = fn.params.map(param => convertParameter(param));
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/ExtractScopeDeclarationsFromDestructuring.ts
+4
@@ -79,6 +79,10 @@ export function extractScopeDeclarationsFromDestructuring(
79
fn: ReactiveFunction,
80
): void {
81
const state = new State(fn.env);
82
+ for (const param of fn.params) {
83
+ const place = param.kind === 'Identifier' ? param : param.place;
84
+ state.declared.add(place.identifier.declarationId);
85
+ }
86
visitReactiveFunction(fn, new Visitor(), state);
87
}
88
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-reassign-props.expect.md
new
+65
@@ -0,0 +1,65 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {Stringify, useIdentity} from 'shared-runtime';
6
+
7
+function Component({other, ...props}, ref) {
8
+ [props, ref] = useIdentity([props, ref]);
9
+ return <Stringify props={props} />;
10
+}
11
+
12
+export const FIXTURE_ENTRYPOINT = {
13
+ fn: Component,
14
+ params: [{a: 0, b: 'hello', children: <div>Hello</div>}],
15
+};
16
+
17
+```
18
+
19
+## Code
20
+
21
+```javascript
22
+import { c as _c } from "react/compiler-runtime";
23
+import { Stringify, useIdentity } from "shared-runtime";
24
+
25
+function Component(t0, ref) {
26
+ const $ = _c(7);
27
+ let props;
28
+ if ($[0] !== t0) {
29
+ let { other, ...t1 } = t0;
30
+ props = t1;
31
+ $[0] = t0;
32
+ $[1] = props;
33
+ } else {
34
+ props = $[1];
35
+ }
36
+ let t1;
37
+ if ($[2] !== props || $[3] !== ref) {
38
+ t1 = [props, ref];
39
+ $[2] = props;
40
+ $[3] = ref;
41
+ $[4] = t1;
42
+ } else {
43
+ t1 = $[4];
44
+ }
45
+ [props, ref] = useIdentity(t1);
46
+ let t2;
47
+ if ($[5] !== props) {
48
+ t2 = <Stringify props={props} />;
49
+ $[5] = props;
50
+ $[6] = t2;
51
+ } else {
52
+ t2 = $[6];
53
+ }
54
+ return t2;
55
+}
56
+
57
+export const FIXTURE_ENTRYPOINT = {
58
+ fn: Component,
59
+ params: [{ a: 0, b: "hello", children: <div>Hello</div> }],
60
+};
61
+
62
+```
63
+
64
+### Eval output
65
+(kind: ok) <div>{"props":{"a":0,"b":"hello","children":{"type":"div","key":null,"props":{"children":"Hello"},"_owner":"[[ cyclic ref *3 ]]","_store":{}}}}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-reassign-props.js
new
+11
@@ -0,0 +1,11 @@
1
+import {Stringify, useIdentity} from 'shared-runtime';
2
+
3
+function Component({other, ...props}, ref) {
4
+ [props, ref] = useIdentity([props, ref]);
5
+ return <Stringify props={props} />;
6
+}
7
+
8
+export const FIXTURE_ENTRYPOINT = {
9
+ fn: Component,
10
+ params: [{a: 0, b: 'hello', children: <div>Hello</div>}],
11
+};