Skip to content

Commit 520042e

Browse files
committed
[compiler] Improve name hints for outlined functions
The previous PR added name hints for anonymous functions, but didn't handle the case of outlined functions. Here we do some cleanup around function `id` and name hints: * Make `HIRFunction.id` a ValidatedIdentifierName, which involved some cleanup of the validation helpers * Add `HIRFunction.nameHint: string` as a place to store the generated name hints which are not valid identifiers * Update Codegen to always use the `id` as the actual function name, and only use nameHint as part of generating the object+property wrapper for debug purposes. This ensures we don't conflate synthesized hints with real function names. Then, we also update OutlineFunctions to use the function name _or_ the nameHint as the input to generating a unique identifier. This isn't quite as nice as the object form since we lose our formatting, but it's a simple step that gives more context to the developer than `_temp` does. Switching to output the object+property lookup form for outlined functions is a bit more involved, let's do that in a follow-up.
1 parent a9410fb commit 520042e

File tree

12 files changed

+155
-64
lines changed

12 files changed

+155
-64
lines changed

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,15 @@ function runWithEnvironment(
325325
outlineJSX(hir);
326326
}
327327

328+
if (env.config.enableNameAnonymousFunctions) {
329+
nameAnonymousFunctions(hir);
330+
log({
331+
kind: 'hir',
332+
name: 'NameAnonymousFunctions',
333+
value: hir,
334+
});
335+
}
336+
328337
if (env.config.enableFunctionOutlining) {
329338
outlineFunctions(hir, fbtOperands);
330339
log({kind: 'hir', name: 'OutlineFunctions', value: hir});
@@ -415,15 +424,6 @@ function runWithEnvironment(
415424
});
416425
}
417426

418-
if (env.config.enableNameAnonymousFunctions) {
419-
nameAnonymousFunctions(hir);
420-
log({
421-
kind: 'hir',
422-
name: 'NameAnonymougFunctions',
423-
value: hir,
424-
});
425-
}
426-
427427
const reactiveFunction = buildReactiveFunction(hir);
428428
log({
429429
kind: 'reactive',

compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
makePropertyLiteral,
4848
makeType,
4949
promoteTemporary,
50+
validateIdentifierName,
5051
} from './HIR';
5152
import HIRBuilder, {Bindings, createTemporaryPlace} from './HIRBuilder';
5253
import {BuiltInArrayId} from './ObjectShape';
@@ -213,6 +214,16 @@ export function lower(
213214
);
214215
}
215216

217+
let validatedId: HIRFunction['id'] = null;
218+
if (id != null) {
219+
const idResult = validateIdentifierName(id);
220+
if (idResult.isErr()) {
221+
builder.errors.merge(idResult.unwrapErr());
222+
} else {
223+
validatedId = idResult.unwrap().value;
224+
}
225+
}
226+
216227
if (builder.errors.hasAnyErrors()) {
217228
return Err(builder.errors);
218229
}
@@ -234,7 +245,8 @@ export function lower(
234245
);
235246

236247
return Ok({
237-
id,
248+
id: validatedId,
249+
nameHint: null,
238250
params,
239251
fnType: bindings == null ? env.fnType : 'Other',
240252
returnTypeAnnotation: null, // TODO: extract the actual return type node if present
@@ -3563,19 +3575,14 @@ function lowerFunctionToValue(
35633575
): InstructionValue {
35643576
const exprNode = expr.node;
35653577
const exprLoc = exprNode.loc ?? GeneratedSource;
3566-
let name: string | null = null;
3567-
if (expr.isFunctionExpression()) {
3568-
name = expr.get('id')?.node?.name ?? null;
3569-
} else if (expr.isFunctionDeclaration()) {
3570-
name = expr.get('id')?.node?.name ?? null;
3571-
}
35723578
const loweredFunc = lowerFunction(builder, expr);
35733579
if (!loweredFunc) {
35743580
return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc};
35753581
}
35763582
return {
35773583
kind: 'FunctionExpression',
3578-
name,
3584+
name: loweredFunc.func.id,
3585+
nameHint: null,
35793586
type: expr.node.type,
35803587
loc: exprLoc,
35813588
loweredFunc,

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
import {BindingKind} from '@babel/traverse';
99
import * as t from '@babel/types';
10-
import {CompilerError} from '../CompilerError';
10+
import {
11+
CompilerDiagnostic,
12+
CompilerError,
13+
ErrorCategory,
14+
} from '../CompilerError';
1115
import {assertExhaustive} from '../Utils/utils';
1216
import {Environment, ReactFunctionType} from './Environment';
1317
import type {HookKind} from './ObjectShape';
@@ -54,7 +58,8 @@ export type SourceLocation = t.SourceLocation | typeof GeneratedSource;
5458
*/
5559
export type ReactiveFunction = {
5660
loc: SourceLocation;
57-
id: string | null;
61+
id: ValidIdentifierName | null;
62+
nameHint: string | null;
5863
params: Array<Place | SpreadPattern>;
5964
generator: boolean;
6065
async: boolean;
@@ -276,7 +281,8 @@ export type ReactiveTryTerminal = {
276281
// A function lowered to HIR form, ie where its body is lowered to an HIR control-flow graph
277282
export type HIRFunction = {
278283
loc: SourceLocation;
279-
id: string | null;
284+
id: ValidIdentifierName | null;
285+
nameHint: string | null;
280286
fnType: ReactFunctionType;
281287
env: Environment;
282288
params: Array<Place | SpreadPattern>;
@@ -1124,7 +1130,8 @@ export type JsxAttribute =
11241130

11251131
export type FunctionExpression = {
11261132
kind: 'FunctionExpression';
1127-
name: string | null;
1133+
name: ValidIdentifierName | null;
1134+
nameHint: string | null;
11281135
loweredFunc: LoweredFunction;
11291136
type:
11301137
| 'ArrowFunctionExpression'
@@ -1301,11 +1308,41 @@ export function forkTemporaryIdentifier(
13011308

13021309
export function validateIdentifierName(
13031310
name: string,
1304-
): Result<ValidIdentifierName, null> {
1305-
if (isReservedWord(name) || !t.isValidIdentifier(name)) {
1306-
return Err(null);
1311+
): Result<ValidatedIdentifier, CompilerError> {
1312+
if (isReservedWord(name)) {
1313+
const error = new CompilerError();
1314+
error.pushDiagnostic(
1315+
CompilerDiagnostic.create({
1316+
category: ErrorCategory.Syntax,
1317+
reason: 'Expected a non-reserved identifier name',
1318+
description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`,
1319+
suggestions: null,
1320+
}).withDetails({
1321+
kind: 'error',
1322+
loc: GeneratedSource,
1323+
message: 'reserved word',
1324+
}),
1325+
);
1326+
return Err(error);
1327+
} else if (!t.isValidIdentifier(name)) {
1328+
const error = new CompilerError();
1329+
error.pushDiagnostic(
1330+
CompilerDiagnostic.create({
1331+
category: ErrorCategory.Syntax,
1332+
reason: `Expected a valid identifier name`,
1333+
description: `\`${name}\` is not a valid JavaScript identifier`,
1334+
suggestions: null,
1335+
}).withDetails({
1336+
kind: 'error',
1337+
loc: GeneratedSource,
1338+
message: 'reserved word',
1339+
}),
1340+
);
13071341
}
1308-
return Ok(makeIdentifierName(name).value);
1342+
return Ok({
1343+
kind: 'named',
1344+
value: name as ValidIdentifierName,
1345+
});
13091346
}
13101347

13111348
/**
@@ -1314,31 +1351,7 @@ export function validateIdentifierName(
13141351
* original source code.
13151352
*/
13161353
export function makeIdentifierName(name: string): ValidatedIdentifier {
1317-
if (isReservedWord(name)) {
1318-
CompilerError.throwInvalidJS({
1319-
reason: 'Expected a non-reserved identifier name',
1320-
loc: GeneratedSource,
1321-
description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`,
1322-
suggestions: null,
1323-
});
1324-
} else {
1325-
CompilerError.invariant(t.isValidIdentifier(name), {
1326-
reason: `Expected a valid identifier name`,
1327-
description: `\`${name}\` is not a valid JavaScript identifier`,
1328-
details: [
1329-
{
1330-
kind: 'error',
1331-
loc: GeneratedSource,
1332-
message: null,
1333-
},
1334-
],
1335-
suggestions: null,
1336-
});
1337-
}
1338-
return {
1339-
kind: 'named',
1340-
value: name as ValidIdentifierName,
1341-
};
1354+
return validateIdentifierName(name).unwrap();
13421355
}
13431356

13441357
/**

compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ export function printFunction(fn: HIRFunction): string {
5656
} else {
5757
definition += '<<anonymous>>';
5858
}
59+
if (fn.nameHint != null) {
60+
definition += ` ${fn.nameHint}`;
61+
}
5962
if (fn.params.length !== 0) {
6063
definition +=
6164
'(' +
@@ -558,7 +561,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
558561
instrValue.loweredFunc.func.aliasingEffects
559562
?.map(printAliasingEffect)
560563
?.join(', ') ?? '';
561-
value = `${kind} ${name} @context[${context}] @aliasingEffects=[${aliasingEffects}]\n${fn}`;
564+
value = `${kind} ${name} ${instrValue.kind === 'FunctionExpression' ? (instrValue.nameHint ?? '') : ''} @context[${context}] @aliasingEffects=[${aliasingEffects}]\n${fn}`;
562565
break;
563566
}
564567
case 'TaggedTemplateExpression': {

compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ function emitSelectorFn(env: Environment, keys: Array<string>): Instruction {
249249
const fn: HIRFunction = {
250250
loc: GeneratedSource,
251251
id: null,
252+
nameHint: null,
252253
fnType: 'Other',
253254
env,
254255
params: [obj],
@@ -275,6 +276,7 @@ function emitSelectorFn(env: Environment, keys: Array<string>): Instruction {
275276
value: {
276277
kind: 'FunctionExpression',
277278
name: null,
279+
nameHint: null,
278280
loweredFunc: {
279281
func: fn,
280282
},

compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ export function outlineFunctions(
3131
) {
3232
const loweredFunc = value.loweredFunc.func;
3333

34-
const id = fn.env.generateGloballyUniqueIdentifierName(loweredFunc.id);
34+
const id = fn.env.generateGloballyUniqueIdentifierName(
35+
loweredFunc.id ?? loweredFunc.nameHint,
36+
);
3537
loweredFunc.id = id.value;
3638

3739
fn.env.outlineFunction(loweredFunc, null);

compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ function emitOutlinedFn(
364364
const fn: HIRFunction = {
365365
loc: GeneratedSource,
366366
id: null,
367+
nameHint: null,
367368
fnType: 'Other',
368369
env,
369370
params: [propsObj],

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveFunction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export function buildReactiveFunction(fn: HIRFunction): ReactiveFunction {
4444
return {
4545
loc: fn.loc,
4646
id: fn.id,
47+
nameHint: fn.nameHint,
4748
params: fn.params,
4849
generator: fn.generator,
4950
async: fn.async,

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import {
4343
ValidIdentifierName,
4444
getHookKind,
4545
makeIdentifierName,
46-
validateIdentifierName,
4746
} from '../HIR/HIR';
4847
import {printIdentifier, printInstruction, printPlace} from '../HIR/PrintHIR';
4948
import {eachPatternOperand} from '../HIR/visitors';
@@ -62,6 +61,7 @@ export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel';
6261
export type CodegenFunction = {
6362
type: 'CodegenFunction';
6463
id: t.Identifier | null;
64+
nameHint: string | null;
6565
params: t.FunctionDeclaration['params'];
6666
body: t.BlockStatement;
6767
generator: boolean;
@@ -384,6 +384,7 @@ function codegenReactiveFunction(
384384
type: 'CodegenFunction',
385385
loc: fn.loc,
386386
id: fn.id !== null ? t.identifier(fn.id) : null,
387+
nameHint: fn.nameHint,
387388
params,
388389
body,
389390
generator: fn.generator,
@@ -2328,10 +2329,6 @@ function codegenInstructionValue(
23282329
reactiveFunction,
23292330
).unwrap();
23302331

2331-
const validatedName =
2332-
instrValue.name != null
2333-
? validateIdentifierName(instrValue.name)
2334-
: Err(null);
23352332
if (instrValue.type === 'ArrowFunctionExpression') {
23362333
let body: t.BlockStatement | t.Expression = fn.body;
23372334
if (body.body.length === 1 && loweredFunc.directives.length == 0) {
@@ -2343,9 +2340,7 @@ function codegenInstructionValue(
23432340
value = t.arrowFunctionExpression(fn.params, body, fn.async);
23442341
} else {
23452342
value = t.functionExpression(
2346-
validatedName
2347-
.map<t.Identifier | null>(name => t.identifier(name))
2348-
.unwrapOr(null),
2343+
instrValue.name != null ? t.identifier(instrValue.name) : null,
23492344
fn.params,
23502345
fn.body,
23512346
fn.generator,
@@ -2354,10 +2349,10 @@ function codegenInstructionValue(
23542349
}
23552350
if (
23562351
cx.env.config.enableNameAnonymousFunctions &&
2357-
validatedName.isErr() &&
2358-
instrValue.name != null
2352+
instrValue.name == null &&
2353+
instrValue.nameHint != null
23592354
) {
2360-
const name = instrValue.name;
2355+
const name = instrValue.nameHint;
23612356
value = t.memberExpression(
23622357
t.objectExpression([t.objectProperty(t.stringLiteral(name), value)]),
23632358
t.stringLiteral(name),

compiler/packages/babel-plugin-react-compiler/src/Transform/NameAnonymousFunctions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ export function nameAnonymousFunctions(fn: HIRFunction): void {
2626
* nesting depth.
2727
*/
2828
const name = `${prefix}${node.generatedName}]`;
29-
node.fn.name = name;
29+
node.fn.nameHint = name;
30+
node.fn.loweredFunc.func.nameHint = name;
3031
}
3132
/**
3233
* Whether or not we generated a name for the function at this node,

0 commit comments

Comments
 (0)