@samitouri / QOS-React / commits / 2c6d92fd80

[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()}