[compiler] Name anonymous functions from inlined useCallbacks (#34586)
@eps1lon flagged this case. Inlined useCallback has an extra LoadLocal indirection which caused us not to add a name. While I was there I added some extra checks to make sure we don't generate names for a given node twice (just in case).
Joseph Savona committed
Sep 24, 2025 at 09:18 UTC
2c6d92fd80ec6917cb7387dbb771e35e82b0126d
3 files changed
+60
-33
compiler/packages/babel-plugin-react-compiler/src/Transform/NameAnonymousFunctions.ts
+8
-3
@@ -19,7 +19,7 @@ export function nameAnonymousFunctions(fn: HIRFunction): void {
19
const parentName = fn.id;
20
const functions = nameAnonymousFunctionsImpl(fn);
21
function visit(node: Node, prefix: string): void {
22
- if (node.generatedName != null) {
22
+ if (node.generatedName != null && node.fn.nameHint == null) {
23
/**
24
* Note that we don't generate a name for functions that already had one,
25
* so we'll only add the prefix to anonymous functions regardless of
@@ -70,6 +70,10 @@ function nameAnonymousFunctionsImpl(fn: HIRFunction): Array<Node> {
70
if (name != null && name.kind === 'named') {
71
names.set(lvalue.identifier.id, name.value);
72
}
73
+ const func = functions.get(value.place.identifier.id);
74
+ if (func != null) {
75
+ functions.set(lvalue.identifier.id, func);
76
+ }
77
break;
78
}
79
case 'PropertyLoad': {
@@ -106,6 +110,7 @@ function nameAnonymousFunctionsImpl(fn: HIRFunction): Array<Node> {
110
const variableName = value.lvalue.place.identifier.name;
111
if (
112
node != null &&
113
+ node.generatedName == null &&
114
variableName != null &&
115
variableName.kind === 'named'
116
) {
@@ -137,7 +142,7 @@ function nameAnonymousFunctionsImpl(fn: HIRFunction): Array<Node> {
142
continue;
143
}
144
const node = functions.get(arg.identifier.id);
140
- if (node != null) {
145
+ if (node != null && node.generatedName == null) {
146
const generatedName =
147
fnArgCount > 1 ? `${calleeName}(arg${i})` : `${calleeName}()`;
148
node.generatedName = generatedName;
@@ -152,7 +157,7 @@ function nameAnonymousFunctionsImpl(fn: HIRFunction): Array<Node> {
157
continue;
158
}
159
const node = functions.get(attr.place.identifier.id);
155
- if (node != null) {
160
+ if (node != null && node.generatedName == null) {
161
const elementName =
162
value.tag.kind === 'BuiltinTag'
163
? value.tag.name
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.expect.md
+45
-28
@@ -4,15 +4,19 @@
4
```javascript
5
// @enableNameAnonymousFunctions
6
7
-import {useEffect} from 'react';
7
+import {useCallback, useEffect} from 'react';
8
import {identity, Stringify, useIdentity} from 'shared-runtime';
9
import * as SharedRuntime from 'shared-runtime';
10
11
function Component(props) {
12
function named() {
13
const inner = () => props.named;
14
- return inner();
14
+ const innerIdentity = identity(() => props.named);
15
+ return inner(innerIdentity());
16
}
17
+ const callback = useCallback(() => {
18
+ return 'ok';
19
+ }, []);
20
const namedVariable = function () {
21
return props.namedVariable;
22
};
@@ -30,6 +34,7 @@ function Component(props) {
34
return (
35
<>
36
{named()}
37
+ {callback()}
38
{namedVariable()}
39
{methodCall()}
40
{call()}
@@ -63,7 +68,7 @@ export const TODO_FIXTURE_ENTRYPOINT = {
68
```javascript
69
import { c as _c } from "react/compiler-runtime"; // @enableNameAnonymousFunctions
70
66
-import { useEffect } from "react";
71
+import { useCallback, useEffect } from "react";
72
import { identity, Stringify, useIdentity } from "shared-runtime";
73
import * as SharedRuntime from "shared-runtime";
74
@@ -75,7 +80,12 @@ function Component(props) {
80
const inner = { "Component[named > inner]": () => props.named }[
81
"Component[named > inner]"
82
];
78
- return inner();
83
+ const innerIdentity = identity(
84
+ { "Component[named > identity()]": () => props.named }[
85
+ "Component[named > identity()]"
86
+ ],
87
+ );
88
+ return inner(innerIdentity());
89
};
90
$[0] = props.named;
91
$[1] = t0;
@@ -83,6 +93,8 @@ function Component(props) {
93
t0 = $[1];
94
}
95
const named = t0;
96
+
97
+ const callback = _ComponentCallback;
98
let t1;
99
if ($[2] !== props.namedVariable) {
100
t1 = {
@@ -197,57 +209,62 @@ function Component(props) {
209
} else {
210
t9 = $[18];
211
}
200
- let t10;
212
+ const t10 = callback();
213
+ let t11;
214
if ($[19] !== namedVariable) {
202
- t10 = namedVariable();
215
+ t11 = namedVariable();
216
$[19] = namedVariable;
204
- $[20] = t10;
217
+ $[20] = t11;
218
} else {
206
- t10 = $[20];
219
+ t11 = $[20];
220
}
208
- const t11 = methodCall();
209
- const t12 = call();
210
- let t13;
221
+ const t12 = methodCall();
222
+ const t13 = call();
223
+ let t14;
224
if ($[21] !== hookArgument) {
212
- t13 = hookArgument();
225
+ t14 = hookArgument();
226
$[21] = hookArgument;
214
- $[22] = t13;
227
+ $[22] = t14;
228
} else {
216
- t13 = $[22];
229
+ t14 = $[22];
230
}
218
- let t14;
231
+ let t15;
232
if (
233
$[23] !== builtinElementAttr ||
234
$[24] !== namedElementAttr ||
222
- $[25] !== t10 ||
223
- $[26] !== t11 ||
224
- $[27] !== t12 ||
225
- $[28] !== t13 ||
235
+ $[25] !== t11 ||
236
+ $[26] !== t12 ||
237
+ $[27] !== t13 ||
238
+ $[28] !== t14 ||
239
$[29] !== t9
240
) {
228
- t14 = (
241
+ t15 = (
242
<>
243
{t9}
244
{t10}
245
{t11}
246
{t12}
247
+ {t13}
248
{builtinElementAttr}
249
{namedElementAttr}
236
- {t13}
250
+ {t14}
251
</>
252
);
253
$[23] = builtinElementAttr;
254
$[24] = namedElementAttr;
241
- $[25] = t10;
242
- $[26] = t11;
243
- $[27] = t12;
244
- $[28] = t13;
255
+ $[25] = t11;
256
+ $[26] = t12;
257
+ $[27] = t13;
258
+ $[28] = t14;
259
$[29] = t9;
246
- $[30] = t14;
260
+ $[30] = t15;
261
} else {
248
- t14 = $[30];
262
+ t15 = $[30];
263
}
250
- return t14;
264
+ return t15;
265
+}
266
+function _ComponentCallback() {
267
+ return "ok";
268
}
269
270
export const TODO_FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.js
+7
-2
@@ -1,14 +1,18 @@
1
// @enableNameAnonymousFunctions
2
3
-import {useEffect} from 'react';
3
+import {useCallback, useEffect} from 'react';
4
import {identity, Stringify, useIdentity} from 'shared-runtime';
5
import * as SharedRuntime from 'shared-runtime';
6
7
function Component(props) {
8
function named() {
9
const inner = () => props.named;
10
- return inner();
10
+ const innerIdentity = identity(() => props.named);
11
+ return inner(innerIdentity());
12
}
13
+ const callback = useCallback(() => {
14
+ return 'ok';
15
+ }, []);
16
const namedVariable = function () {
17
return props.namedVariable;
18
};
@@ -26,6 +30,7 @@ function Component(props) {
30
return (
31
<>
32
{named()}
33
+ {callback()}
34
{namedVariable()}
35
{methodCall()}
36
{call()}