From aa7193720bfa51684f66c455f21757829890bc72 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 24 Sep 2025 08:55:40 -0700 Subject: [PATCH] [compiler] Name anonymous functions from inlined useCallbacks @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). --- .../src/Transform/NameAnonymousFunctions.ts | 11 ++- .../name-anonymous-functions.expect.md | 73 ++++++++++++------- .../compiler/name-anonymous-functions.js | 9 ++- 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/NameAnonymousFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/NameAnonymousFunctions.ts index cf6d443b907ad..23f9ed729f653 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/NameAnonymousFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/NameAnonymousFunctions.ts @@ -19,7 +19,7 @@ export function nameAnonymousFunctions(fn: HIRFunction): void { const parentName = fn.id; const functions = nameAnonymousFunctionsImpl(fn); function visit(node: Node, prefix: string): void { - if (node.generatedName != null) { + if (node.generatedName != null && node.fn.nameHint == null) { /** * Note that we don't generate a name for functions that already had one, * so we'll only add the prefix to anonymous functions regardless of @@ -70,6 +70,10 @@ function nameAnonymousFunctionsImpl(fn: HIRFunction): Array { if (name != null && name.kind === 'named') { names.set(lvalue.identifier.id, name.value); } + const func = functions.get(value.place.identifier.id); + if (func != null) { + functions.set(lvalue.identifier.id, func); + } break; } case 'PropertyLoad': { @@ -106,6 +110,7 @@ function nameAnonymousFunctionsImpl(fn: HIRFunction): Array { const variableName = value.lvalue.place.identifier.name; if ( node != null && + node.generatedName == null && variableName != null && variableName.kind === 'named' ) { @@ -137,7 +142,7 @@ function nameAnonymousFunctionsImpl(fn: HIRFunction): Array { continue; } const node = functions.get(arg.identifier.id); - if (node != null) { + if (node != null && node.generatedName == null) { const generatedName = fnArgCount > 1 ? `${calleeName}(arg${i})` : `${calleeName}()`; node.generatedName = generatedName; @@ -152,7 +157,7 @@ function nameAnonymousFunctionsImpl(fn: HIRFunction): Array { continue; } const node = functions.get(attr.place.identifier.id); - if (node != null) { + if (node != null && node.generatedName == null) { const elementName = value.tag.kind === 'BuiltinTag' ? value.tag.name diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.expect.md index 88e270647d3e6..6fccad7685b20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.expect.md @@ -4,15 +4,19 @@ ```javascript // @enableNameAnonymousFunctions -import {useEffect} from 'react'; +import {useCallback, useEffect} from 'react'; import {identity, Stringify, useIdentity} from 'shared-runtime'; import * as SharedRuntime from 'shared-runtime'; function Component(props) { function named() { const inner = () => props.named; - return inner(); + const innerIdentity = identity(() => props.named); + return inner(innerIdentity()); } + const callback = useCallback(() => { + return 'ok'; + }, []); const namedVariable = function () { return props.namedVariable; }; @@ -30,6 +34,7 @@ function Component(props) { return ( <> {named()} + {callback()} {namedVariable()} {methodCall()} {call()} @@ -63,7 +68,7 @@ export const TODO_FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableNameAnonymousFunctions -import { useEffect } from "react"; +import { useCallback, useEffect } from "react"; import { identity, Stringify, useIdentity } from "shared-runtime"; import * as SharedRuntime from "shared-runtime"; @@ -75,7 +80,12 @@ function Component(props) { const inner = { "Component[named > inner]": () => props.named }[ "Component[named > inner]" ]; - return inner(); + const innerIdentity = identity( + { "Component[named > identity()]": () => props.named }[ + "Component[named > identity()]" + ], + ); + return inner(innerIdentity()); }; $[0] = props.named; $[1] = t0; @@ -83,6 +93,8 @@ function Component(props) { t0 = $[1]; } const named = t0; + + const callback = _ComponentCallback; let t1; if ($[2] !== props.namedVariable) { t1 = { @@ -197,57 +209,62 @@ function Component(props) { } else { t9 = $[18]; } - let t10; + const t10 = callback(); + let t11; if ($[19] !== namedVariable) { - t10 = namedVariable(); + t11 = namedVariable(); $[19] = namedVariable; - $[20] = t10; + $[20] = t11; } else { - t10 = $[20]; + t11 = $[20]; } - const t11 = methodCall(); - const t12 = call(); - let t13; + const t12 = methodCall(); + const t13 = call(); + let t14; if ($[21] !== hookArgument) { - t13 = hookArgument(); + t14 = hookArgument(); $[21] = hookArgument; - $[22] = t13; + $[22] = t14; } else { - t13 = $[22]; + t14 = $[22]; } - let t14; + let t15; if ( $[23] !== builtinElementAttr || $[24] !== namedElementAttr || - $[25] !== t10 || - $[26] !== t11 || - $[27] !== t12 || - $[28] !== t13 || + $[25] !== t11 || + $[26] !== t12 || + $[27] !== t13 || + $[28] !== t14 || $[29] !== t9 ) { - t14 = ( + t15 = ( <> {t9} {t10} {t11} {t12} + {t13} {builtinElementAttr} {namedElementAttr} - {t13} + {t14} ); $[23] = builtinElementAttr; $[24] = namedElementAttr; - $[25] = t10; - $[26] = t11; - $[27] = t12; - $[28] = t13; + $[25] = t11; + $[26] = t12; + $[27] = t13; + $[28] = t14; $[29] = t9; - $[30] = t14; + $[30] = t15; } else { - t14 = $[30]; + t15 = $[30]; } - return t14; + return t15; +} +function _ComponentCallback() { + return "ok"; } export const TODO_FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.js index ff4f1f6017d98..963bee9ea6f7e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.js @@ -1,14 +1,18 @@ // @enableNameAnonymousFunctions -import {useEffect} from 'react'; +import {useCallback, useEffect} from 'react'; import {identity, Stringify, useIdentity} from 'shared-runtime'; import * as SharedRuntime from 'shared-runtime'; function Component(props) { function named() { const inner = () => props.named; - return inner(); + const innerIdentity = identity(() => props.named); + return inner(innerIdentity()); } + const callback = useCallback(() => { + return 'ok'; + }, []); const namedVariable = function () { return props.namedVariable; }; @@ -26,6 +30,7 @@ function Component(props) { return ( <> {named()} + {callback()} {namedVariable()} {methodCall()} {call()}