Keep the names of functions boxed inside records and arrays

A function value converted to unknown on its own takes its name from the
conversion site, but one held in a record, an array, a map or another
converted value had no name there, so util.inspect printed
`[Function (anonymous)]` where Node prints `[Function: toString]`.

The function box now looks a nameless function up in a per-module table
of (entry point, name) pairs, built from the functions the module makes
closures of: each declared or lifted function's sourceName, which follows
NamedEvaluation, and for an object literal's method its key
(IrFunction.displayName). An adapter is looked up through the function it
adapts. displayName is set only in modules that box a value that can hold
a function, and the table exists only in modules that box functions.
This commit is contained in:
Chris Tate
2026-10-30 07:40:27 -05:00
parent 2ca0ca75e9
commit 717ddfdfe3
12 changed files with 310 additions and 27 deletions
+100 -4
View File
@@ -223,6 +223,9 @@ export interface DynHost extends WalkerHost {
/** The closures with a jsval body (IrFunction.jsEntry) on targets with
* jsval words, which the box builders give a jsval entry. */
jsEntryClosures?(): readonly JsEntryClosure[];
/** The entry point and JavaScript name of each named function the module
* makes closures of (LlEmitter.closureNames). */
closureNames?(): readonly { entry: string; name: string }[];
}
/** A closure with a jsval body (IrFunction.jsEntry), for the box builders. */
@@ -309,6 +312,8 @@ export class LlDyn {
return this.dynFuncBoxHelper(args[0] as IrType & { kind: "func" });
case "dyn.jsEntryThunk":
return this.jsEntryThunk(args[0] as string, args[1] as IrType[], args[2] as IrType);
case "dyn.unnamedFuncBoxHelper":
return this.unnamedFuncBoxHelper(args[0] as IrType & { kind: "func" }) ?? "";
case "dyn.dynFuncAdapterHelper":
return this.dynFuncAdapterHelper(args[0] as IrType & { kind: "func" });
case "dyn.dynToStrHelper":
@@ -3561,7 +3566,7 @@ export class LlDyn {
}
case "func": {
const r = B.tmp();
B.line(`${r} = call ptr @${this.dynFuncBoxHelper(t)}(ptr %v, ptr null)`);
B.line(`${r} = ${this.unnamedFuncBoxCall(t, "%v")}`);
B.terminate(`ret ptr ${r}`);
break;
}
@@ -3972,7 +3977,7 @@ export class LlDyn {
// function boundary (the dynFrom func special case, sans name).
const p = emitUnionPeek(B, nullable, "%v");
const r = B.tmp();
B.line(`${r} = call ptr @${this.dynFuncBoxHelper(arm)}(ptr ${p}, ptr null)`);
B.line(`${r} = ${this.unnamedFuncBoxCall(arm, p)}`);
B.terminate(`ret ptr ${r}`);
} else {
const p = emitUnionPeek(B, nullable, "%v");
@@ -5359,9 +5364,8 @@ export class LlDyn {
private toDynExpr(B: BlockBuilder, t: IrType, expr: string): string {
if (t.kind === "dyn") return this.retainDyn(B, expr);
if (t.kind === "func") {
const box = this.dynFuncBoxHelper(t);
const r = B.tmp();
B.line(`${r} = call ptr @${box}(ptr ${expr}, ptr null)`);
B.line(`${r} = ${this.unnamedFuncBoxCall(t, expr)}`);
return r;
}
@@ -5651,6 +5655,98 @@ export class LlDyn {
return name;
}
private closureNames: { symbol: string; count: number } | null | undefined;
/** The module's closure-name lookup (`sc_closure_name(closure)`, over a
* table of `{ ptr entry, ptr name }` pairs), defined on first use; null
* when no named function becomes a closure. */
private closureNameTable(): { symbol: string; count: number } | null {
if (this.closureNames !== undefined) return this.closureNames;
const entries = this.host.closureNames?.() ?? [];
if (entries.length === 0) return (this.closureNames = null);
const host = this.host;
const S = host.sizeType;
const table = "sc_closure_names";
const symbol = "sc_closure_name";
// The closure's entry point (an adapter's root's, ScrClosure.identity)
// compared with each entry; null when none matches.
this.defs.push(
`@${table} = internal constant [${entries.length} x { ptr, ptr }] [${entries
.map((e) => `{ ptr, ptr } { ptr @${e.entry}, ptr ${host.cstr(e.name)} }`)
.join(", ")}]`,
``,
`define internal ptr @${symbol}(ptr %v) ${FN_ATTRS} { ; the JavaScript name of a closure's function`,
`entry:`,
` %idp = getelementptr inbounds %ScrClosure, ptr %v, i64 0, i32 6`,
` %id = load ptr, ptr %idp`,
` %own = icmp eq ptr %id, null`,
` %root = select i1 %own, ptr %v, ptr %id`,
` %fnp = getelementptr inbounds %ScrClosure, ptr %root, i64 0, i32 1`,
` %fn = load ptr, ptr %fnp`,
` br label %loop`,
`loop:`,
` %i = phi ${S} [ 0, %entry ], [ %next, %step ]`,
` %done = icmp eq ${S} %i, ${entries.length}`,
` br i1 %done, label %none, label %test`,
`test:`,
` %ep = getelementptr inbounds [${entries.length} x { ptr, ptr }], ptr @${table}, i64 0, ${S} %i, i32 0`,
` %e = load ptr, ptr %ep`,
` %hit = icmp eq ptr %e, %fn`,
` br i1 %hit, label %found, label %step`,
`step:`,
` %next = add ${S} %i, 1`,
` br label %loop`,
`found:`,
` %np = getelementptr inbounds [${entries.length} x { ptr, ptr }], ptr @${table}, i64 0, ${S} %i, i32 1`,
` %name = load ptr, ptr %np`,
` ret ptr %name`,
`none:`,
` ret ptr null`,
`}`,
``,
);
return (this.closureNames = { symbol, count: entries.length });
}
private readonly unnamedFuncBoxes = new Map<string, string>();
/** `sc_dfu_<n>(ptr closure) -> ptr`: the box of a function value with no
* static name (one inside a record, array or other value being
* converted), named from the module's closure-name table. The plain box
* when no named function becomes a closure. */
unnamedFuncBoxHelper(t: IrType & { kind: "func" }): string | null {
if ((this.host.closureNames?.() ?? []).length === 0) return null;
const box = this.dynFuncBoxHelper(t);
const key = typeKey(t);
const existing = this.unnamedFuncBoxes.get(key);
if (existing) return existing;
const name = `sc_dfu_${this.unnamedFuncBoxes.size}`;
this.unnamedFuncBoxes.set(key, name);
this.host.journalAllocated?.(name, "dyn.unnamedFuncBoxHelper", [t]);
// The first box defines the table and its lookup.
const table = this.closureNameTable()!;
this.defs.push(
`define internal ptr @${name}(ptr %v) ${FN_ATTRS} { ; box ${key} into dyn with its own name`,
`entry:`,
` %n = call ptr @${table.symbol}(ptr %v)`,
` %r = call ptr @${box}(ptr %v, ptr %n)`,
` ret ptr %r`,
`}`,
``,
);
this.host.journalBuilt?.();
return name;
}
/** The call that boxes the function value `value` of type `t` without a
* static name (unnamedFuncBoxHelper). */
unnamedFuncBoxCall(t: IrType & { kind: "func" }, value: string): string {
const named = this.unnamedFuncBoxHelper(t);
return named === null
? `call ptr @${this.dynFuncBoxHelper(t)}(ptr ${value}, ptr null)`
: `call ptr @${named}(ptr ${value})`;
}
/** The box builder dynFrom emits for one closure signature. */
dynFuncBoxHelper(t: IrType & { kind: "func" }): string {
const key = typeKey(t);
@@ -924,6 +924,7 @@ const POLL_EVERY_ITERATION = "%worker.poll.every";
/** Runtime library calls (readsLoopMetrics). */
const LIB_CALL_KINDS: readonly IrNodeKind[] = ["libCall"];
const CLOSURE_KINDS: readonly IrNodeKind[] = ["closure"];
/** [A-Za-z0-9_] */
function isIdentifierByte(code: number): boolean {
@@ -2465,6 +2466,7 @@ export class LlEmitter {
);
},
pendingTestLines: (dest) => this.pendingTestLines(dest),
closureNames: () => this.closureNames(),
virtualEntry: (implFn) => this.virtualEntry(implFn),
programLifetime: this.programLifetime,
regionAliasTag: (word) => this.memoryAliasAttachment(`region.${word}`),
@@ -2808,6 +2810,33 @@ export class LlEmitter {
return this.heapCensus === null ? out : [...out, this.heapCensus.definitions().join("\n")];
}
/** The entry point and JavaScript name of each named function the
* module makes closures of: a declared function's wrapper (its interned
* closure's entry) or a lifted function's call target, with its
* displayName, else its sourceName. Function boxes without a static name
* look the closure's entry up in this table. */
private closureNameEntries: readonly { entry: string; name: string }[] | undefined;
closureNames(): readonly { entry: string; name: string }[] {
if (this.closureNameEntries !== undefined) return this.closureNameEntries;
const entries = new Map<string, string>();
const scan = this.nodeIndex.scan(CLOSURE_KINDS);
for (const fn of this.mod.functions) {
eachNodeOfKinds(fn, scan, (node) => {
if (node.kind !== "closure") return true;
const target = this.fnByName.get(node.fnName);
const name =
target?.displayName ??
(target?.sourceName !== "<anonymous>" ? target?.sourceName : undefined);
if (target === undefined || name === undefined || name === "") return true;
const entry =
target.captures === undefined ? mangleWrapper(node.fnName) : this.callTarget(node.fnName);
if (!entries.has(entry)) entries.set(entry, name);
return true;
});
}
return (this.closureNameEntries = [...entries].map(([entry, name]) => ({ entry, name })));
}
/** The dispose-observer gates the emitted frees and record writers test
* (alloc.ts): defined after every helper is emitted, because dynCheck
* builders, JSON schemas and weak collection constructors are requested
@@ -8598,6 +8627,7 @@ for (const op of [
"dyn.dynFuncThunkHelper",
"dyn.dynFuncBoxHelper",
"dyn.jsEntryThunk",
"dyn.unnamedFuncBoxHelper",
"dyn.dynFuncAdapterHelper",
"dyn.dynToStrHelper",
"dyn.dynIterNHelper",
@@ -120,10 +120,12 @@ export function emitDynamicExpr(
// A closure boxes as the checked-dynamic tree's function kind: retained closure +
// the per-signature call thunk + the interned signature key. The
// best-effort name rides along (null when the lowering had none).
const name = e.fnName !== undefined && e.fnName !== "" ? host.cstr(e.fnName) : "null";
const box = host.dyn.dynFuncBoxHelper(v.type);
const t = B.tmp();
B.line(`${t} = call ptr @${box}(ptr ${v.name}, ptr ${name})`);
if (e.fnName !== undefined && e.fnName !== "") {
const name = host.cstr(e.fnName);
const box = host.dyn.dynFuncBoxHelper(v.type);
B.line(`${t} = call ptr @${box}(ptr ${v.name}, ptr ${name})`);
} else B.line(`${t} = ${host.dyn.unnamedFuncBoxCall(v.type, v.name)}`);
if (e.fnLength !== undefined) {
host.declare(`declare void @scr_dyn_function_set_length(ptr, i32)`);
B.line(`call void @scr_dyn_function_set_length(ptr ${t}, i32 ${e.fnLength})`);
@@ -0,0 +1,99 @@
/* The JavaScript names of object literals' methods.
*
* A function boxed into a checked value inside a record, an array or
* another converted value has no static name at the box, so the backend
* takes it from the function (LlEmitter.closureNames): its sourceName, or
* for an object literal's method, whose stack-frame name differs, its
* displayName. displayName is set only in modules that box such a
* function, so other modules lower as before. */
import * as ts from "../ts7/adapter.js";
import type { IrFunction, IrType } from "../../ir/ir.js";
import { typeKey } from "../../ir/ir.js";
import { everyStmtList } from "../../ir/traverse.js";
import type { Lowerer } from "./lowerer.js";
/** Notes the name of the lifted function `fnName` when `node` is a method
* of an object literal with a static key. */
export function noteObjectMethodName(lowerer: Lowerer, node: ts.Node, fnName: string): void {
if (!ts.isMethodDeclaration(node) || !ts.isObjectLiteralExpression(node.parent)) return;
const name = node.name;
if (ts.isIdentifier(name) || ts.isStringLiteral(name))
lowerer.objectMethodNames.set(fnName, name.text);
else if (ts.isNumericLiteral(name))
lowerer.objectMethodNames.set(fnName, String(Number(name.text)));
}
/** Gives the noted methods their displayName when the module boxes a
* function without a static name, or a value that can hold one. */
export function finalizeFunctionNames(lowerer: Lowerer, functions: readonly IrFunction[]): void {
if (lowerer.objectMethodNames.size === 0) return;
const seen = new Map<string, boolean>();
let boxes = false;
for (const fn of functions) {
everyStmtList(fn.body, {
stmt: () => !boxes,
expr: (e) => {
// A function boxed on its own with a static name needs none.
if (
e.kind === "dynFrom" &&
(e.value.type.kind === "func"
? e.fnName === undefined || e.fnName === ""
: reachesFunction(lowerer, e.value.type, seen))
)
boxes = true;
return !boxes;
},
});
if (boxes) break;
}
if (!boxes) return;
for (const fn of functions) {
const name = lowerer.objectMethodNames.get(fn.name);
if (name !== undefined) fn.displayName = name;
}
}
/** Whether a value of `t` can hold a function value. */
function reachesFunction(lowerer: Lowerer, t: IrType, seen: Map<string, boolean>): boolean {
const key = typeKey(t);
const known = seen.get(key);
if (known !== undefined) return known;
seen.set(key, false);
let result = false;
switch (t.kind) {
case "func":
result = true;
break;
case "record": {
const shape = lowerer.shapes.get(t.shapeId);
result =
(shape?.fields.some((field) => reachesFunction(lowerer, field.type, seen)) ?? false) ||
(shape?.indexValue !== undefined && reachesFunction(lowerer, shape.indexValue, seen));
break;
}
case "array":
case "set":
result = reachesFunction(lowerer, t.elem, seen);
break;
case "map":
result = reachesFunction(lowerer, t.key, seen) || reachesFunction(lowerer, t.value, seen);
break;
case "union":
result = (lowerer.unions.get(t.unionId)?.arms ?? []).some((arm) =>
reachesFunction(lowerer, arm, seen),
);
break;
case "promise":
result = reachesFunction(lowerer, t.inner, seen);
break;
case "object":
result = [...(lowerer.classes.get(t.className)?.fields.values() ?? [])].some((field) =>
reachesFunction(lowerer, field, seen),
);
break;
default:
result = false;
}
seen.set(key, result);
return result;
}
@@ -40,6 +40,7 @@ import {
import { generatorMeta, isThisParameter, type ParamShape } from "./call-signatures.js";
import { isStrictCode } from "./strict-mode.js";
import { lowerSloppyGlobalReceiver } from "./lower-global-value.js";
import { noteObjectMethodName } from "./function-names.js";
/** Save the call-site receiver before defaults or body effects. Escaping
* arrows capture this binding instead of reading a later call's receiver. */
@@ -550,6 +551,7 @@ export function lowerLambda(
if (isAsync) lifted.async = true;
if (fnCtx.generator) lifted.generator = fnCtx.generator;
lowerer.liftedFns.push(lifted);
noteObjectMethodName(lowerer, node, fnName);
return { kind: "closure", fnName, captures: ctx.captureSources, type: funcType, loc };
} catch (e) {
// JS sources defer LAMBDA poisons like function declarations
@@ -249,6 +249,7 @@ import {
import { DeferredModuleInitializers } from "./deferred-module-initializers.js";
import { finalizeClassMethodValues } from "./class-method-values.js";
import { finalizePrivateBrands } from "./private-brands.js";
import { finalizeFunctionNames } from "./function-names.js";
import { type ClassSymbolKey, INSPECT_CUSTOM_MEMBER, tableKeyFieldType } from "./symbol-fields.js";
import type { HttpClientFnBinding } from "./lower-server.js";
export type { WidthLift } from "./width-lift.js";
@@ -3826,6 +3827,9 @@ export class Lowerer {
>();
/** Classes used as ThreadSlots keys: their hierarchies carry a slot word. */
readonly threadSlotKeys = new Set<string>();
/** The JavaScript names of object literals' methods, by lifted function
* (function-names.ts). */
readonly objectMethodNames = new Map<string, string>();
readonly forkTargetIdByPath = new Map<string, number>();
/** Namespace ModuleBlocks this program lowers, filled by splitFiles:
* "flattened" — an instantiated namespace whose body joined the file's
@@ -4447,6 +4451,7 @@ export class Lowerer {
checkedTable("threadSlotNames", () => this.threadSlotNames.size),
checkedTable("threadSlotFamilies", () => this.threadSlotFamilies.size),
checkedTable("threadSlotKeys", () => this.threadSlotKeys.size),
checkedTable("objectMethodNames", () => this.objectMethodNames.size),
checkedTable("forkTargetIdByPath", () => this.forkTargetIdByPath.size),
checkedTable("nsBlocks", () => this.nsBlocks.size),
checkedTable("initNameOf", () => this.initNameOf.size),
@@ -9816,6 +9821,7 @@ export class Lowerer {
// one is exact only if the declaration is held at a single signature.
checkGenericIdentityPins(this);
boxBuiltinValuesChecked(this, functions);
finalizeFunctionNames(this, functions);
if (this.diags.length === 0) fuseArrayPipelines(this, functions);
finalizeClassMethodValues(this, functions);
finalizePrivateBrands(this, functions);
+26 -20
View File
@@ -5,7 +5,7 @@ import type { IrReader, IrWriter } from "./codec.js";
/** A digest of the coders below: an encoding written with other coders is
* refused instead of misread, also after a change that keeps IR_VERSION. */
export const IR_CODEC_LAYOUT = "c8e9ba1a465defc2";
export const IR_CODEC_LAYOUT = "23df825e49b75d14";
/** Writes `mod` field by field (codec.ts describes the bytes). */
export function encodeIrModule(w: IrWriter, mod: IrModule): void {
@@ -25,7 +25,7 @@ export function decodeIrFunction(r: IrReader): IrFunction {
}
const KEYS0: readonly string[] = ["irVersion","sourceFile","functions","classes","globals","records","unions","entry","workers","declBoundary","inferredFactFiles","declOrigins","lifetimeRegions","foreignElementKeys","threadSlotKeys","freezableArrays","sloppyDynWrites","untypedIntegrity","commonJsEntry","nodeIsland","ffiImports","lib","contracts","int32ArrayCandidates","wasmModules","wasmUntranslated","wasmCustomSections"];
const KEYS1: readonly string[] = ["name","sourceName","ownsPrototype","params","returnType","locals","captures","classCaptures","body","loc","jsEntry","tier","speculativeDispatch","fastRead","plainLoops","async","generator","asyncCacheGlobal","asyncCycleCacheGlobal"];
const KEYS1: readonly string[] = ["name","sourceName","displayName","ownsPrototype","params","returnType","locals","captures","classCaptures","body","loc","jsEntry","tier","speculativeDispatch","fastRead","plainLoops","async","generator","asyncCacheGlobal","asyncCycleCacheGlobal"];
const KEYS2: readonly string[] = ["libnode","nodeVersion","modules"];
const KEYS3: readonly string[] = ["profileName","prefix","initSymbol","sinkRegisterSymbol","collectSymbol","resultResetSymbol","threadInstances","callbackRegisterSymbol","callbacks","exports","trapOverlays","identity"];
const KEYS4: readonly string[] = ["int32Fields","int32Params","int32Returns","int32Locals","int32Elements","noThrow","acyclic","immortalGlobals","programLifetime","inlineArrays"];
@@ -404,6 +404,11 @@ function encIrFunction(w: IrWriter, v: IrFunction): void {
w.byte(1);
w.str(v.sourceName);
}
if (v.displayName === undefined) w.byte(0);
else {
w.byte(1);
w.str(v.displayName);
}
if (v.ownsPrototype === undefined) w.byte(0);
else {
w.byte(1);
@@ -473,24 +478,25 @@ function encIrFunction(w: IrWriter, v: IrFunction): void {
function decIrFunction(r: IrReader): IrFunction {
const f0 = r.str();
const f1 = r.tag(2, "IrFunction.sourceName") === 0 ? undefined : r.str();
const f2 = r.tag(2, "IrFunction.ownsPrototype") === 0 ? undefined : (r.bool() as NonNullable<IrFunction["ownsPrototype"]>);
const f3 = decT15(r);
const f4 = decIrType(r);
const f5 = decT17(r);
const f6 = r.tag(2, "IrFunction.captures") === 0 ? undefined : decT15(r);
const f7 = r.tag(2, "IrFunction.classCaptures") === 0 ? undefined : decT18(r);
const f8 = decT19(r);
const f9 = decSrcLoc(r);
const f10 = r.tag(2, "IrFunction.jsEntry") === 0 ? undefined : r.str();
const f11 = r.tag(2, "IrFunction.tier") === 0 ? undefined : (r.str() as NonNullable<IrFunction["tier"]>);
const f12 = r.tag(2, "IrFunction.speculativeDispatch") === 0 ? undefined : r.bool();
const f13 = r.tag(2, "IrFunction.fastRead") === 0 ? undefined : (r.str() as NonNullable<IrFunction["fastRead"]>);
const f14 = r.tag(2, "IrFunction.plainLoops") === 0 ? undefined : (r.bool() as NonNullable<IrFunction["plainLoops"]>);
const f15 = r.tag(2, "IrFunction.async") === 0 ? undefined : (r.bool() as NonNullable<IrFunction["async"]>);
const f16 = r.tag(2, "IrFunction.generator") === 0 ? undefined : decT21(r);
const f17 = r.tag(2, "IrFunction.asyncCacheGlobal") === 0 ? undefined : r.str();
const f18 = r.tag(2, "IrFunction.asyncCycleCacheGlobal") === 0 ? undefined : r.str();
const o: IrFunction = { name: f0, ...(f1 !== undefined ? { sourceName: f1 } : {}), ...(f2 !== undefined ? { ownsPrototype: f2 } : {}), params: f3, returnType: f4, locals: f5, ...(f6 !== undefined ? { captures: f6 } : {}), ...(f7 !== undefined ? { classCaptures: f7 } : {}), body: f8, loc: f9, ...(f10 !== undefined ? { jsEntry: f10 } : {}), ...(f11 !== undefined ? { tier: f11 } : {}), ...(f12 !== undefined ? { speculativeDispatch: f12 } : {}), ...(f13 !== undefined ? { fastRead: f13 } : {}), ...(f14 !== undefined ? { plainLoops: f14 } : {}), ...(f15 !== undefined ? { async: f15 } : {}), ...(f16 !== undefined ? { generator: f16 } : {}), ...(f17 !== undefined ? { asyncCacheGlobal: f17 } : {}), ...(f18 !== undefined ? { asyncCycleCacheGlobal: f18 } : {}) };
const f2 = r.tag(2, "IrFunction.displayName") === 0 ? undefined : r.str();
const f3 = r.tag(2, "IrFunction.ownsPrototype") === 0 ? undefined : (r.bool() as NonNullable<IrFunction["ownsPrototype"]>);
const f4 = decT15(r);
const f5 = decIrType(r);
const f6 = decT17(r);
const f7 = r.tag(2, "IrFunction.captures") === 0 ? undefined : decT15(r);
const f8 = r.tag(2, "IrFunction.classCaptures") === 0 ? undefined : decT18(r);
const f9 = decT19(r);
const f10 = decSrcLoc(r);
const f11 = r.tag(2, "IrFunction.jsEntry") === 0 ? undefined : r.str();
const f12 = r.tag(2, "IrFunction.tier") === 0 ? undefined : (r.str() as NonNullable<IrFunction["tier"]>);
const f13 = r.tag(2, "IrFunction.speculativeDispatch") === 0 ? undefined : r.bool();
const f14 = r.tag(2, "IrFunction.fastRead") === 0 ? undefined : (r.str() as NonNullable<IrFunction["fastRead"]>);
const f15 = r.tag(2, "IrFunction.plainLoops") === 0 ? undefined : (r.bool() as NonNullable<IrFunction["plainLoops"]>);
const f16 = r.tag(2, "IrFunction.async") === 0 ? undefined : (r.bool() as NonNullable<IrFunction["async"]>);
const f17 = r.tag(2, "IrFunction.generator") === 0 ? undefined : decT21(r);
const f18 = r.tag(2, "IrFunction.asyncCacheGlobal") === 0 ? undefined : r.str();
const f19 = r.tag(2, "IrFunction.asyncCycleCacheGlobal") === 0 ? undefined : r.str();
const o: IrFunction = { name: f0, ...(f1 !== undefined ? { sourceName: f1 } : {}), ...(f2 !== undefined ? { displayName: f2 } : {}), ...(f3 !== undefined ? { ownsPrototype: f3 } : {}), params: f4, returnType: f5, locals: f6, ...(f7 !== undefined ? { captures: f7 } : {}), ...(f8 !== undefined ? { classCaptures: f8 } : {}), body: f9, loc: f10, ...(f11 !== undefined ? { jsEntry: f11 } : {}), ...(f12 !== undefined ? { tier: f12 } : {}), ...(f13 !== undefined ? { speculativeDispatch: f13 } : {}), ...(f14 !== undefined ? { fastRead: f14 } : {}), ...(f15 !== undefined ? { plainLoops: f15 } : {}), ...(f16 !== undefined ? { async: f16 } : {}), ...(f17 !== undefined ? { generator: f17 } : {}), ...(f18 !== undefined ? { asyncCacheGlobal: f18 } : {}), ...(f19 !== undefined ? { asyncCycleCacheGlobal: f19 } : {}) };
return o;
}
@@ -135,6 +135,7 @@ const FUNCTION_FIELDS: Record<StringFields<Omit<IrFunction, "body">>, Role> = {
name: "other",
sourceName: "other",
jsEntry: "reference",
displayName: "other",
asyncCacheGlobal: "other",
asyncCycleCacheGlobal: "other",
"params[].name": "other",
+4
View File
@@ -1966,6 +1966,10 @@ export interface IrFunction {
name: string;
/** Source function name for native error stack frames. */
sourceName?: string;
/** The function's JavaScript `name` when it differs from sourceName: an
* object literal's method. Present only in modules that box function
* values into checked values (function-names.ts). */
displayName?: string;
/** Ordinary authored function declarations/expressions own a prototype. */
ownsPrototype?: true;
params: IrParam[];
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/boxed-function-names.ts"
],
"diags": []
}
+30
View File
@@ -0,0 +1,30 @@
// Functions held in records, arrays and maps keep their JavaScript names
// when the value is converted to unknown: util.inspect prints them.
function declared() {
return 0;
}
const named = function inner() {
return 1;
};
const arrow = () => 2;
const wide = {
a: 1,
toString() {
return "w";
},
h: () => 3,
g: named,
d: declared,
"quoted"() {
return 4;
},
};
const box: unknown = wide;
console.log(box);
const list: unknown = [declared, named, arrow];
console.log(list);
const byName = new Map<string, () => number>([["a", arrow]]);
const mapBox: unknown = byName;
console.log(mapBox);
const plain: unknown = { f: function () { return 5; } };
console.log(plain);
@@ -0,0 +1 @@
{ "plain": 1946, "san": 3000 }