Hold Reflect.ownKeys's function value in the Reflect namespace object

Reading `ownKeys` from Reflect read as a value (`const r = Reflect;
r.ownKeys`) gave the member function that throws SC1090 when called,
although the spelled `Reflect.ownKeys` read already has a function
value. The namespace object now holds that function value, so calls
through the alias run and `r.ownKeys === Reflect.ownKeys` is true, as in
Node. The spelled read and the namespace member share one lowering
(reflectMemberValue).
This commit is contained in:
Chris Tate
2026-10-24 20:07:36 -05:00
parent 102c86b6d2
commit e2db1ef344
4 changed files with 28 additions and 15 deletions
@@ -24,6 +24,7 @@ import type { Lowerer } from "./lowerer.js";
import { InternalCompilerError } from "../../errors.js";
import { mathMemberValue } from "./lower-stdlib.js";
import { jsonMemberValue } from "./builtins/json.js";
import { reflectMemberValue } from "./builtins/reflection.js";
/** A function member with its `length`, or a constant (`length` -1). */
interface NamespaceMember {
@@ -244,7 +245,7 @@ function functionValue(lowerer: Lowerer, canonical: string, key: string, loc: Sr
? mathMemberValue(lowerer, key, loc)
: canonical === "JSON"
? jsonMemberValue(lowerer, key, loc)
: null;
: reflectMemberValue(lowerer, key, loc);
if (value !== null)
return value.type.kind === "dyn"
? value
@@ -3,7 +3,24 @@ import * as ts from "../../ts7/adapter.js";
import { type Lowerer } from "../lowerer.js";
import { locOf } from "../../program.js";
import { builtinModuleFnOf } from "../surfaces.js";
import { BOOL, DYN, type IrExpr, STRING, arrayOf } from "../../../ir/ir.js";
import { BOOL, DYN, type IrExpr, STRING, type SrcLoc, arrayOf } from "../../../ir/ir.js";
/** The function value of the Reflect member `member` (ownKeys, on native
* computed-key reflection), or null for members without one. */
export function reflectMemberValue(lowerer: Lowerer, member: string, loc: SrcLoc): IrExpr | null {
if (member !== "ownKeys") return null;
const value = lowerer.lowerNativeCallableValue(
{
fn: "dyn.ownKeys",
params: [DYN],
result: DYN,
valueParams: [{ mode: "required", type: DYN }],
},
"Reflect.ownKeys",
loc,
)!;
return { kind: "dynFrom", value, fnName: "ownKeys", type: DYN, loc };
}
/** Reflect.ownKeys uses native computed-key reflection. Reflect.apply(target,
* thisArg, argsList) where the TARGET is a builtin
@@ -161,6 +161,7 @@ import { lowerNetAddressStaticRead } from "../builtins/net-address.js";
import { STATS_TYPE_TESTS } from "../builtins/filesystem.js";
import { arrayValueRead, arrayValueType } from "../array-values.js";
import { lowerClassMethodValue } from "../class-method-values.js";
import { reflectMemberValue } from "../builtins/reflection.js";
import {
objLitGenericFnInfoOf,
objLitGenericFnNodeOf,
@@ -1691,19 +1692,8 @@ export function lowerIntrinsicProperty(
);
}
}
if (lowerer.stdlibGlobalMember(expr, "Reflect") === "ownKeys") {
const value = lowerer.lowerNativeCallableValue(
{
fn: "dyn.ownKeys",
params: [DYN],
result: DYN,
valueParams: [{ mode: "required", type: DYN }],
},
"Reflect.ownKeys",
locOf(expr),
)!;
return { kind: "dynFrom", value, fnName: "ownKeys", type: DYN, loc: locOf(expr) };
}
if (lowerer.stdlibGlobalMember(expr, "Reflect") === "ownKeys")
return reflectMemberValue(lowerer, "ownKeys", locOf(expr));
if (lowerer.chainBlocked(expr)) return null;
// Object.prototype.constructor: the Object constructor value.
if (
@@ -20,3 +20,8 @@ console.log(parse("[1,2]").length, JSON.stringify({ a: [1] }));
console.log(typeof Math === "object" && typeof Reflect === "object");
console.log(Math.max.length, Math.min.length, Math.hypot.length, Math.abs.length);
const r = Reflect;
const ownKeys = r.ownKeys;
console.log(ownKeys === Reflect.ownKeys, r.ownKeys === Reflect.ownKeys, typeof ownKeys);
console.log(r.ownKeys({ a: 1, b: 2 }).length, ownKeys({ c: 3 })[0], ownKeys.name, ownKeys.length);