mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-03 07:54:40 +08:00
reflection: Reflect.has, and Object reflection over records
Reflect.has had no lowering. It is [[HasProperty]] — own and inherited names (Array.prototype's, Object.prototype's, a class's compiled methods), proxy `has` traps, symbol and property-key conversion — which is exactly the `in` operator's checked-dynamic probe, so it lowers to dyn.hasKeyComputed over the target's dyn view (and the target is fenced when it has no such view). A primitive target is Node's TypeError, which the probe answers false for instead: fenced by name, never silently divergent. Object.getOwnPropertyNames/Symbols/Descriptors only accepted values that already were checked-dynamic; a record, array or class instance now boxes into its own dyn view (own fields and elements, in property order) like the function and error targets already did. Corpus 8426 pins both against Node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1a3caa88eb
commit
4f7c48b4e3
@@ -62,6 +62,40 @@ export function lowerReflectCall(
|
||||
}
|
||||
if (call.questionDotToken) return null;
|
||||
const reflectMember = lowerer.stdlibGlobalMember(access, "Reflect");
|
||||
// Reflect.has(target, key) — the `in` operator's presence query over
|
||||
// the target's checked-dynamic view (own and inherited names, proxy
|
||||
// `has` traps included). A primitive target is Node's TypeError
|
||||
// ("Reflect.has called on non-object"), which the presence query
|
||||
// answers false for instead: fenced, never silently divergent.
|
||||
if (
|
||||
reflectMember === "has" &&
|
||||
call.arguments.nodes.length === 2 &&
|
||||
!call.arguments.nodes.some(ts.isSpreadElement)
|
||||
) {
|
||||
const loc = locOf(call);
|
||||
const target = lowerer.lowerExpr(call.arguments.nodes[0]!);
|
||||
const primitive =
|
||||
isUnitType(target.type) ||
|
||||
["bool", "f64", "string", "bigint", "symbol"].includes(target.type.kind);
|
||||
if (primitive)
|
||||
lowerer.noLowering(
|
||||
"Reflect.has on a primitive target",
|
||||
call.arguments.nodes[0]!,
|
||||
"Node throws a TypeError for a primitive target; narrow to an object first",
|
||||
);
|
||||
if (!lowerer.dynConvertible(target.type))
|
||||
lowerer.noLowering("Reflect.has on this target", call.arguments.nodes[0]!);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "dyn.hasKeyComputed",
|
||||
args: [
|
||||
lowerer.coerceToExpected(target, DYN),
|
||||
lowerer.lowerExprExpecting(call.arguments.nodes[1]!, DYN),
|
||||
],
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
// Reflect.getOwnPropertyDescriptor and Reflect.defineProperty over
|
||||
// checked objects (execa's Promise mixin): the Object operations, with
|
||||
// defineProperty answering true. A non-object target throws the Object
|
||||
|
||||
@@ -7885,6 +7885,10 @@ function lowerObjectStaticCall(
|
||||
isUnitType(object.type)
|
||||
)
|
||||
object = { kind: "dynFrom", value: object, type: DYN, loc: locOf(call) };
|
||||
// A record, array or class instance reflects through its own
|
||||
// checked-dynamic view (own fields and elements, in property order).
|
||||
if (object.type.kind !== "dyn" && lowerer.dynConvertible(object.type))
|
||||
object = lowerer.coerceToExpected(object, DYN);
|
||||
if (object.type.kind === "dyn")
|
||||
return {
|
||||
kind: "libCall",
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8426-reflect-object-reflections.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Reflect.has over the checked-dynamic view, and the Object reflection
|
||||
// operations over records, arrays and class instances (own fields and
|
||||
// elements in property order). Reflect.has is [[HasProperty]], so inherited
|
||||
// names (Array.prototype's, Object.prototype's, a class's methods) answer
|
||||
// true; a primitive target is Node's TypeError and stays a lowerer fence
|
||||
// (SC2020) rather than a silent `false`.
|
||||
class Point {
|
||||
x = 1;
|
||||
y = 2;
|
||||
sum(): number {
|
||||
return this.x + this.y;
|
||||
}
|
||||
}
|
||||
const record = { a: 1, b: "two" };
|
||||
const list = [10, 20];
|
||||
const point = new Point();
|
||||
console.log(Reflect.has(record, "a"), Reflect.has(record, "z"));
|
||||
console.log(Reflect.has(list, "0"), Reflect.has(list, "length"), Reflect.has(list, "9"));
|
||||
console.log(Reflect.has(point, "x"), Reflect.has(point, "sum"));
|
||||
const key = "b";
|
||||
console.log(Reflect.has(record, key), Reflect.has(record, key.toUpperCase()));
|
||||
console.log(Reflect.has(record, "toString"), Reflect.has(record, "constructor"));
|
||||
console.log(Object.getOwnPropertyNames(record).join(","));
|
||||
console.log(Object.getOwnPropertyNames(point).join(","));
|
||||
console.log(Object.getOwnPropertyNames(list).length);
|
||||
const descriptors = Object.getOwnPropertyDescriptors(record);
|
||||
console.log(Object.keys(descriptors).join(","));
|
||||
console.log(Object.getOwnPropertySymbols(record).length);
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3121, "san": 5843 }
|
||||
Reference in New Issue
Block a user