Name functions stored in JavaScript object literals as Node (#1591)

This commit is contained in:
Chris Tate
2026-10-20 20:17:19 -05:00
committed by GitHub
4 changed files with 67 additions and 1 deletions
@@ -20,7 +20,7 @@ import type { IrExpr, IrLocal, IrRecordShape, IrStmt, IrType, SrcLoc } from "../
import { isCjsExportTableLiteral, isJsSourceFile, locOf } from "../../program.js";
import { NARROW_FIRST } from "../surfaces.js";
import { recordShapeMismatchDiag } from "../../../diagnostics/diagnostic.js";
import { PoisonError, neverTaintedJsType } from "../lowerer.js";
import { PoisonError, jsFuncNameOf, neverTaintedJsType } from "../lowerer.js";
import type { Lowerer } from "../lowerer.js";
import { lowerIndexMergeHelper } from "../containers/indexed-objects.js";
import type { IndexMergeContributor } from "../containers/indexed-objects.js";
@@ -188,6 +188,16 @@ function propNameText(lowerer: Lowerer, name: ts.PropertyName): string {
return (name as ts.Identifier | ts.StringLiteral).text;
}
/** The name an anonymous function or a method takes from its property key
* (NamedEvaluation): an identifier, string or numeric key, or a computed
* key that folds to one string. Null for other keys. */
function propertyFunctionName(lowerer: Lowerer, name: ts.PropertyName): string | null {
if (ts.isComputedPropertyName(name)) return literalComputedKey(lowerer, name);
if (ts.isNumericLiteral(name)) return String(Number(name.text));
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
return null;
}
/** The runtime-keyed JS object literal (a computed key that doesn't fold):
* builds a dyn object member-by-member. Keys evaluate before their values,
* properties in source order — JS's object-literal evaluation exactly.
@@ -406,6 +416,26 @@ export function lowerDynObjectLiteral(
let v = boxValue
? boxValue(valueExpr as ts.Expression, raw)
: lowerer.coerceToExpected(raw, DYN);
// A function that boxes into a property takes its name as Node gives
// it: a function read by name keeps that name (`{ f }`, `{ k: f }`),
// and an anonymous function or a method takes the property's name
// (`{ g: function () {} }`, `{ m() {} }`).
if (
!boxValue &&
v.kind === "dynFrom" &&
v.value.type.kind === "func" &&
v.fnName === undefined
) {
let anonymous: ts.Node = valueExpr;
while (ts.isParenthesizedExpression(anonymous)) anonymous = anonymous.expression;
const name =
ts.isMethodDeclaration(prop) ||
((ts.isFunctionExpression(anonymous) || ts.isArrowFunction(anonymous)) &&
!(ts.isFunctionExpression(anonymous) && anonymous.name))
? propertyFunctionName(lowerer, prop.name!)
: jsFuncNameOf(valueExpr, lowerer);
if (name !== null) v = { ...v, fnName: name };
}
// A JS object's array-valued properties retain the same source array.
// Repeated references must compare equal and mutations remain shared.
if (
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6760-object-literal-function-names.cjs"
],
"diags": []
}
@@ -0,0 +1,29 @@
'use strict';
// A function stored in an object literal's property keeps the name Node
// gives it: a function read by name keeps that name, and an anonymous
// function, arrow or method takes the property's key.
function f() {}
const g = function () {};
const h = () => 1;
const named = function inner() {};
const o = {
f,
g,
h,
k: f,
anon: function () {},
arrow: () => 2,
'a-b': () => 3,
['computed']: () => 4,
m() {},
named,
inline: function own() {},
};
for (const key of Object.keys(o)) console.log(key, JSON.stringify(o[key].name));
console.log(o);
const mixed = { n: 1, s: 'x', f, g: function () {} };
console.log(mixed, mixed.g.name);
function show(value) {
console.log(value);
}
show({ f, m() {} });
@@ -0,0 +1 @@
{ "plain": 2630, "san": 3350 }