mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 00:25:34 +08:00
Expand static native object and array semantics (#569)
* Support primitive and function property descriptors * Dispatch literal bracket calls to native class methods * Handle numeric class method keys in bracket calls * Call function values returned by class accessors * Expand native object and array semantics * Keep new corpus fixtures aligned with compiler gates * Lower Object.isFrozen arguments once
This commit is contained in:
@@ -4645,10 +4645,32 @@ function emitDynamicLibCall(state: LibCallState): Temp {
|
||||
// Object.create(null): the fresh null-prototype dictionary
|
||||
// (+1). Never throws.
|
||||
return finish(`scr_dyn_new_obj_null_proto()`);
|
||||
case "dyn.objCreate":
|
||||
return finish(`scr_dyn_obj_create(${arg(0)})`);
|
||||
case "dyn.objCreateWithProperties":
|
||||
return finish(`scr_dyn_obj_create_with_properties(${arg(0)}, ${arg(1)})`);
|
||||
case "dyn.getPrototype":
|
||||
return finish(`scr_dyn_get_prototype(${arg(0)})`);
|
||||
case "dyn.setPrototype":
|
||||
return finish(`scr_dyn_set_prototype(${arg(0)}, ${arg(1)})`);
|
||||
case "dyn.getOwnPropertyNames":
|
||||
return finish(`scr_dyn_get_own_property_names(${arg(0)})`);
|
||||
case "dyn.getOwnPropertyDescriptors":
|
||||
return finish(`scr_dyn_get_own_property_descriptors(${arg(0)})`);
|
||||
case "dyn.preventExtensions":
|
||||
return finish(`scr_dyn_prevent_extensions(${arg(0)})`);
|
||||
case "dyn.isExtensible":
|
||||
return finish(`scr_dyn_is_extensible(${arg(0)})`);
|
||||
case "dyn.seal":
|
||||
return finish(`scr_dyn_seal(${arg(0)})`);
|
||||
case "dyn.isSealed":
|
||||
return finish(`scr_dyn_is_sealed(${arg(0)})`);
|
||||
case "dyn.hasOwn":
|
||||
// Object.hasOwn over a dyn receiver (throws on nullish, like
|
||||
// Node's ToObject).
|
||||
return finish(`scr_dyn_has_own(${arg(0)}, ${arg(1)})`);
|
||||
case "dyn.propertyIsEnumerable":
|
||||
return finish(`scr_dyn_property_is_enumerable(${arg(0)}, ${arg(1)})`);
|
||||
case "dyn.objValues":
|
||||
return finish(`scr_dyn_obj_values(${arg(0)})`);
|
||||
case "dyn.objEntries":
|
||||
|
||||
@@ -1134,6 +1134,8 @@ export function jsonWriteHelper(emitter: CEmitter, t: IrType): string {
|
||||
d.push(` return r;`);
|
||||
d.push(` }`);
|
||||
d.push(` }`);
|
||||
d.push(` if (d->kind == SCR_DYN_ARR && d->v.arr.properties)`);
|
||||
d.push(` return scr_dyn_obj_read(d->v.arr.properties, k->data, k->len);`);
|
||||
d.push(` }`);
|
||||
d.push(` return scr_dyn_retain(scr_dyn_undefined());`);
|
||||
d.push(`}`, ``);
|
||||
|
||||
@@ -2674,6 +2674,15 @@ export class LlDyn {
|
||||
B.line(`call void @scr_str_release(ptr ${ch})`);
|
||||
B.terminate(`ret ptr ${r2}`);
|
||||
B.startBlock(lMiss);
|
||||
const lArrNamed = B.newLabel("kg.an");
|
||||
const lOther = B.newLabel("kg.ao");
|
||||
B.condBr(isA, lArrNamed, lOther);
|
||||
B.startBlock(lArrNamed);
|
||||
host.declare(`declare ptr @scr_dyn_arr_named_get(ptr, ptr)`);
|
||||
const named = B.tmp();
|
||||
B.line(`${named} = call ptr @scr_dyn_arr_named_get(ptr %d, ptr %k)`);
|
||||
B.terminate(`ret ptr ${named}`);
|
||||
B.startBlock(lOther);
|
||||
retainUndef();
|
||||
}
|
||||
B.startBlock(lNext);
|
||||
|
||||
@@ -168,14 +168,14 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
const retTy = host.llType(e.type);
|
||||
const retDecl = retTy === "i1" ? "zeroext i1" : retTy;
|
||||
host.declare(`declare ${retDecl} @${sym}(${argDecls.join(", ")})`);
|
||||
const argList = args.map((a) => `${host.llType(a.type)} ${a.name}`).join(", ");
|
||||
const argList = args.map((a, index) => `${argDecls[index]} ${a.name}`).join(", ");
|
||||
if (retTy === "void") {
|
||||
B.line(`call void @${sym}(${argList})`);
|
||||
if (MAY_THROW_LIB_FNS.has(e.fn)) host.emitPendingCheck();
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ${retTy} @${sym}(${argList})`);
|
||||
B.line(`${t} = call ${retDecl} @${sym}(${argList})`);
|
||||
// The result joins its frame BEFORE the pending check so an unwind
|
||||
// releases the dummy (NULL for refcounted returns) harmlessly.
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
|
||||
@@ -871,6 +871,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.objKeys": "scr_dyn_obj_keys",
|
||||
"dyn.forInKeys": "scr_dyn_for_in_keys",
|
||||
"dyn.hasOwn": "scr_dyn_has_own",
|
||||
"dyn.propertyIsEnumerable": "scr_dyn_property_is_enumerable",
|
||||
"dyn.assign": "scr_dyn_assign",
|
||||
"dyn.copyDataProperties": "scr_dyn_copy_data_properties",
|
||||
// variadic Object.assign: the source pack (push never throws; the
|
||||
@@ -881,6 +882,16 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.packPushSpreadIter": "scr_dyn_pack_push_spread_iter",
|
||||
"dyn.assignAll": "scr_dyn_assign_all",
|
||||
"dyn.objCreateNullProto": "scr_dyn_new_obj_null_proto",
|
||||
"dyn.objCreate": "scr_dyn_obj_create",
|
||||
"dyn.objCreateWithProperties": "scr_dyn_obj_create_with_properties",
|
||||
"dyn.getPrototype": "scr_dyn_get_prototype",
|
||||
"dyn.setPrototype": "scr_dyn_set_prototype",
|
||||
"dyn.getOwnPropertyNames": "scr_dyn_get_own_property_names",
|
||||
"dyn.getOwnPropertyDescriptors": "scr_dyn_get_own_property_descriptors",
|
||||
"dyn.preventExtensions": "scr_dyn_prevent_extensions",
|
||||
"dyn.isExtensible": "scr_dyn_is_extensible",
|
||||
"dyn.seal": "scr_dyn_seal",
|
||||
"dyn.isSealed": "scr_dyn_is_sealed",
|
||||
"dyn.objValues": "scr_dyn_obj_values",
|
||||
"dyn.objEntries": "scr_dyn_obj_entries",
|
||||
"dyn.structuredClone": "scr_structured_clone",
|
||||
|
||||
@@ -8,7 +8,7 @@ import { AstNode } from "../ts7/ast-node.js";
|
||||
import { bodyReadsArguments } from "../arguments-usage.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import { lowerGenMethodCall } from "./lower-generators.js";
|
||||
import { BYTES_ELEMENT_NAME, BIGINT_T, BOOL, CAUGHT, DYN, F64, type IrExpr, type IrFunction, type IrLocal, type IrParam, type IrStmt, type IrType, JSVAL, STRING, SYMBOL_T, type SrcLoc, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, canDynCheckTo, canMarshalTypedFuncIntoIsland, ffiClassType, ffiSourceParamTypes, funcOf, isFfiCallbackParam, isFfiContextParam, isFfiReleaseParam, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/ir.js";
|
||||
import { BYTES_ELEMENT_NAME, BIGINT_T, BOOL, CAUGHT, DYN, F64, type IrExpr, type IrFunction, type IrLocal, type IrParam, type IrStmt, type IrType, JSVAL, NULL_T, STRING, SYMBOL_T, type SrcLoc, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, canDynCheckTo, canMarshalTypedFuncIntoIsland, ffiClassType, ffiSourceParamTypes, funcOf, isFfiCallbackParam, isFfiContextParam, isFfiReleaseParam, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/ir.js";
|
||||
import type { IrFfiCallbackParam, IrFfiCallbackParamClass, IrFfiImport, IrFfiReleaseParam } from "../../ir/ir.js";
|
||||
import { isJsSourceFile, isNodeEsmFile, locOf, requireSpecOf } from "../program.js";
|
||||
import { genResultRecord, isGenericCallableMemberType, jsOpenObjectType, typeKey } from "../type-mapper.js";
|
||||
@@ -4825,6 +4825,18 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
// monomorphized against the defining literal's declaration.
|
||||
lowerObjLitGenericMethodCall(lowerer, expr, expr.expression);
|
||||
if (intrinsic) return intrinsic;
|
||||
const accessorTarget = lowerer.fieldTarget(expr.expression);
|
||||
if (accessorTarget?.container === "accessor") {
|
||||
const callee = lowerer.fieldGetExpr(accessorTarget, locOf(expr.expression), expr.expression);
|
||||
if (callee.type.kind === "func") {
|
||||
const args = completeFuncValueArgs(lowerer, expr, callee.type, locOf(expr));
|
||||
return { kind: "callValue", callee, args, type: callee.type.ret, loc: locOf(expr) };
|
||||
}
|
||||
if (callee.type.kind === "dyn" && !expr.arguments.some((arg) => ts.isSpreadElement(arg))) {
|
||||
const args = expr.arguments.map((arg) => lowerer.lowerExprExpecting(arg, DYN));
|
||||
return { kind: "dynCall", callee, calleeName: expr.expression.getText(), args, type: DYN, loc: locOf(expr) };
|
||||
}
|
||||
}
|
||||
// A method call rooted at an initializer-less ambient `declare
|
||||
// const/var` whose declared type has no mapping: Node throws the
|
||||
// catchable ReferenceError at the ROOT read before the member, the
|
||||
@@ -4898,9 +4910,38 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
ts.isElementAccessExpression(expr.expression) &&
|
||||
!expr.expression.questionDotToken &&
|
||||
!expr.questionDotToken &&
|
||||
ts.isStringLiteralLike(expr.expression.argumentExpression)
|
||||
(ts.isStringLiteralLike(expr.expression.argumentExpression) || ts.isNumericLiteral(expr.expression.argumentExpression))
|
||||
) {
|
||||
const memberName = expr.expression.argumentExpression.text;
|
||||
const memberName = ts.isNumericLiteral(expr.expression.argumentExpression)
|
||||
? String(Number(expr.expression.argumentExpression.text))
|
||||
: expr.expression.argumentExpression.text;
|
||||
const receiverType = lowerer.mapTypeOf(lowerer.typeOf(expr.expression.expression));
|
||||
if (receiverType?.kind === "object") {
|
||||
const info = lowerer.classes.get(receiverType.className);
|
||||
const found = info ? lowerer.findMethodOn(info, memberName) : null;
|
||||
if (info && found) {
|
||||
const virtual = lowerer.overrideBelow(info, memberName);
|
||||
if (found.sig.abstract === true && !virtual) {
|
||||
lowerer.unsupported("SC1090", expr, `calls of the abstract method '${memberName}' with no concrete implementation below the receiver's static class`);
|
||||
}
|
||||
if (virtual) lowerer.noteVirtualEdge(info, memberName);
|
||||
else lowerer.noteEdge(`%${found.declarer.def.name}.${memberName}`);
|
||||
const receiver = lowerer.lowerExpr(expr.expression.expression);
|
||||
const args = lowerer.completeArgs(expr.arguments, found.sig.params, locOf(expr), expr);
|
||||
const callArgs = [lowerer.upcastTo(receiver, virtual ? info.def.name : found.declarer.def.name), ...args];
|
||||
const loc = locOf(expr);
|
||||
if (virtual) {
|
||||
return reconcileOverloadReturn(lowerer, expr, {
|
||||
kind: "virtualCall", className: info.def.name, method: memberName,
|
||||
args: callArgs, type: found.sig.ret, loc,
|
||||
});
|
||||
}
|
||||
return reconcileOverloadReturn(lowerer, expr, {
|
||||
kind: "call", callee: `%${found.declarer.def.name}.${memberName}`,
|
||||
args: callArgs, type: found.sig.ret, loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
// ts7's getSymbolAtLocation does not resolve element accesses; the
|
||||
// member symbol comes from the receiver's (apparent) type instead —
|
||||
// same provenance answer as the dot spelling's name symbol.
|
||||
@@ -5344,8 +5385,8 @@ function immediateObjectTagReceiver(lowerer: Lowerer, node: ts.Expression): { ta
|
||||
}
|
||||
|
||||
const ARRAY_BORROW_METHODS = new Set([
|
||||
"at", "every", "filter", "find", "findIndex", "forEach", "includes",
|
||||
"indexOf", "lastIndexOf", "map", "reduce", "reduceRight", "some",
|
||||
"at", "every", "filter", "find", "findIndex", "findLast", "findLastIndex", "forEach", "includes",
|
||||
"indexOf", "lastIndexOf", "map", "reduce", "reduceRight", "slice", "some",
|
||||
]);
|
||||
|
||||
function lowerArrayPrototypeBorrowCall(
|
||||
@@ -5401,7 +5442,11 @@ function lowerObjectOwnMethodCall(
|
||||
const method = access.name.text;
|
||||
if (lowerer.dynamic || call.questionDotToken || access.questionDotToken ||
|
||||
(method !== "hasOwnProperty" && method !== "propertyIsEnumerable") ||
|
||||
!lowerer.isStdlibMember(access) || call.arguments.some(ts.isSpreadElement)) return null;
|
||||
call.arguments.some(ts.isSpreadElement)) return null;
|
||||
if (!lowerer.isStdlibMember(access)) {
|
||||
const receiver = tryLowerExpression(lowerer, access.expression);
|
||||
if (receiver?.type.kind !== "dyn") return null;
|
||||
}
|
||||
return lowerObjectOwnCall(lowerer, call, access.expression, call.arguments[0], call.arguments.slice(1), method);
|
||||
}
|
||||
|
||||
@@ -5476,8 +5521,8 @@ function lowerObjectOwnCall(
|
||||
lowerer.noLowering("Object.prototype." + method + " over this record shape", receiverNode ?? call);
|
||||
}
|
||||
result = { kind: "call", callee: recordHasOwnHelper(lowerer, type.shapeId, loc), args: [receiverRef, key], type: BOOL, loc };
|
||||
} else if (type.kind === "dyn" && !enumerable) {
|
||||
result = { kind: "libCall", fn: "dyn.hasOwn", args: [receiverRef, key], type: BOOL, loc };
|
||||
} else if (type.kind === "dyn") {
|
||||
result = { kind: "libCall", fn: enumerable ? "dyn.propertyIsEnumerable" : "dyn.hasOwn", args: [receiverRef, key], type: BOOL, loc };
|
||||
} else if (type.kind === "string" || type.kind === "array") {
|
||||
const needleLocal = lowerer.declareHiddenLocal("%ownIndex", F64);
|
||||
stmts.push({ kind: "varDecl", localId: needleLocal.id, init: { kind: "libCall", fn: "num.fromString", args: [key], type: F64, loc }, loc });
|
||||
@@ -8829,6 +8874,28 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
if (call.questionDotToken || access.questionDotToken) return null;
|
||||
if (!lowerer.isStdlibGlobal(access.expression, "Object")) return null;
|
||||
const member = access.name.text;
|
||||
if ((member === "getOwnPropertyNames" || member === "getOwnPropertyDescriptors") && call.arguments.length === 1 &&
|
||||
!call.arguments.some(ts.isSpreadElement) && !lowerer.dynamic) {
|
||||
let object = lowerer.lowerExpr(call.arguments[0]!);
|
||||
if (object.type.kind === "string" || object.type.kind === "f64" ||
|
||||
object.type.kind === "bool" || isUnitType(object.type))
|
||||
object = { kind: "dynFrom", value: object, type: DYN, loc: locOf(call) };
|
||||
if (object.type.kind === "dyn")
|
||||
return { kind: "libCall", fn: member === "getOwnPropertyNames" ? "dyn.getOwnPropertyNames" : "dyn.getOwnPropertyDescriptors", args: [object], type: DYN, loc: locOf(call) };
|
||||
}
|
||||
if ((member === "getPrototypeOf" || member === "setPrototypeOf") &&
|
||||
call.arguments.length === (member === "getPrototypeOf" ? 1 : 2) &&
|
||||
!call.arguments.some(ts.isSpreadElement) && !lowerer.dynamic) {
|
||||
const object = lowerer.lowerExpr(call.arguments[0]!);
|
||||
if (object.type.kind === "dyn") {
|
||||
const loc = locOf(call);
|
||||
if (member === "getPrototypeOf")
|
||||
return { kind: "libCall", fn: "dyn.getPrototype", args: [object], type: DYN, loc };
|
||||
const prototype = lowerer.lowerExpr(call.arguments[1]!);
|
||||
if (prototype.type.kind === "dyn")
|
||||
return { kind: "libCall", fn: "dyn.setPrototype", args: [object, prototype], type: DYN, loc };
|
||||
}
|
||||
}
|
||||
if (member === "getOwnPropertyDescriptor" && call.arguments.length === 2) {
|
||||
const [receiver, key] = call.arguments;
|
||||
if (receiver && key && ts.isPropertyAccessExpression(receiver) && receiver.name.text === "prototype" &&
|
||||
@@ -8959,27 +9026,14 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
"the operands must share one comparable kind (numbers, strings, booleans, units, one union shape, or one reference type)",
|
||||
);
|
||||
}
|
||||
// Object.create — the null-prototype DICTIONARY (`Object.create(null)`
|
||||
// then keyed assignment, the memo-table idiom prettier's index/
|
||||
// group-mode maps spell) and, under --dynamic, the engine's own
|
||||
// Object.create for engine-held prototypes. Everything else is a
|
||||
// NAMED fence: the compiled representations have no prototype chain,
|
||||
// and the own-copy stand-in would answer WRONG observably — Node's
|
||||
// Object.keys/inspect/JSON of the created object list NO own keys,
|
||||
// and mutating the prototype afterwards is visible through the
|
||||
// created object (live delegation), which no copy can honor.
|
||||
// Native checked objects retain a live prototype and can install an
|
||||
// own-property descriptor table during creation. Engine-held values
|
||||
// use the engine's Object.create path below.
|
||||
if (member === "create") {
|
||||
if (call.arguments.some((a) => ts.isSpreadElement(a))) {
|
||||
lowerer.noLowering("Object.create with spread arguments", call);
|
||||
}
|
||||
if (call.arguments.length >= 2) {
|
||||
lowerer.noLowering(
|
||||
"Object.create with a properties-descriptor argument",
|
||||
call,
|
||||
"create first, then assign: const o = Object.create(null); o.k = v",
|
||||
);
|
||||
}
|
||||
if (call.arguments.length !== 1) {
|
||||
if (call.arguments.length < 1 || call.arguments.length > 2) {
|
||||
lowerer.noLowering(`Object.create with ${call.arguments.length} arguments`, call);
|
||||
}
|
||||
const loc = locOf(call);
|
||||
@@ -8987,6 +9041,7 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
while (ts.isParenthesizedExpression(protoNode)) protoNode = protoNode.expression;
|
||||
const nullProto = protoNode.kind === ts.SyntaxKind.NullKeyword;
|
||||
if (lowerer.dynamic) {
|
||||
if (call.arguments.length === 2) lowerer.noLowering("Object.create with properties under --dynamic", call);
|
||||
// The checker types the result `any` — an ENGINE value under
|
||||
// --dynamic — and the engine's own Object.create answers with
|
||||
// REAL prototype semantics: reads delegate LIVE, writes shadow,
|
||||
@@ -9011,15 +9066,24 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
"prototype reads delegate LIVE in Node (mutating the prototype shows through the created object), which the boundary's deep copy cannot honor — only null and engine-held ('any') prototypes lower",
|
||||
);
|
||||
}
|
||||
if (nullProto) {
|
||||
if (nullProto && call.arguments.length === 1) {
|
||||
return { kind: "libCall", fn: "dyn.objCreateNullProto", args: [], type: DYN, loc };
|
||||
}
|
||||
const proto = lowerer.lowerExpr(protoNode);
|
||||
lowerer.noLowering(
|
||||
`Object.create over '${lowerer.fmt(proto.type)}' prototypes`,
|
||||
call,
|
||||
"the compiled representations have no prototype chain, and an own-copy would answer wrong observably (Node lists NO own keys on the created object, and prototype mutations show through it live) — only Object.create(null) lowers",
|
||||
);
|
||||
let proto = nullProto
|
||||
? { kind: "dynFrom", value: { kind: "unitLit", unit: "null", type: NULL_T, loc }, type: DYN, loc } as IrExpr
|
||||
: ts.isObjectLiteralExpression(protoNode)
|
||||
? lowerer.lowerExprExpecting(protoNode, DYN)
|
||||
: lowerer.lowerExpr(protoNode);
|
||||
if (isUnitType(proto.type) || proto.type.kind === "f64" || proto.type.kind === "bool" || proto.type.kind === "string")
|
||||
proto = { kind: "dynFrom", value: proto, type: DYN, loc };
|
||||
if (proto.type.kind === "dyn") {
|
||||
if (call.arguments.length === 1)
|
||||
return { kind: "libCall", fn: "dyn.objCreate", args: [proto], type: DYN, loc };
|
||||
const descriptors = lowerer.lowerExprExpecting(call.arguments[1]!, DYN);
|
||||
if (descriptors.type.kind === "dyn")
|
||||
return { kind: "libCall", fn: "dyn.objCreateWithProperties", args: [proto, descriptors], type: DYN, loc };
|
||||
}
|
||||
lowerer.noLowering(`Object.create over '${lowerer.fmt(proto.type)}' prototypes`, call);
|
||||
}
|
||||
// `Object.assign(fn, { props })` whose RESULT type maps to the hybrid
|
||||
// (function-with-properties) record: the chalk-shape CONSTRUCTOR.
|
||||
@@ -9201,7 +9265,9 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
const fnName = jsFuncNameOf(call.arguments[0]!);
|
||||
target = { kind: "dynFrom", value: target, type: DYN, loc: locOf(call.arguments[0]!), ...(fnName !== null ? { fnName } : {}) };
|
||||
}
|
||||
if (!target || (target.type.kind !== "dyn" && !isUnitType(target.type))) return null;
|
||||
const descriptorPrimitive = member === "getOwnPropertyDescriptor" && target &&
|
||||
(target.type.kind === "bool" || target.type.kind === "f64" || target.type.kind === "string");
|
||||
if (!target || (target.type.kind !== "dyn" && !isUnitType(target.type) && !descriptorPrimitive)) return null;
|
||||
if (target.type.kind !== "dyn") target = { kind: "dynFrom", value: target, type: DYN, loc: locOf(call.arguments[0]!) };
|
||||
const key = lowerer.lowerExprExpecting(call.arguments[1]!, DYN);
|
||||
if (key.type.kind !== "dyn") return null;
|
||||
@@ -9244,6 +9310,28 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (["preventExtensions", "isExtensible", "seal", "isSealed"].includes(member) &&
|
||||
call.arguments.length === 1 && !ts.isSpreadElement(call.arguments[0]!)) {
|
||||
const value = lowerer.lowerExpr(call.arguments[0]!);
|
||||
if (value.type.kind === "string" || value.type.kind === "f64" ||
|
||||
value.type.kind === "bool" || value.type.kind === "symbol" || isUnitType(value.type)) {
|
||||
if (member === "preventExtensions" || member === "seal") return value;
|
||||
const loc = locOf(call);
|
||||
const answer: IrExpr = { kind: "boolLit", value: member !== "isExtensible", type: BOOL, loc };
|
||||
return isSafeToDiscard(value) ? answer : {
|
||||
kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: value, loc: value.loc }],
|
||||
result: answer, type: BOOL, loc,
|
||||
};
|
||||
}
|
||||
if (value.type.kind === "dyn") {
|
||||
const operation = {
|
||||
preventExtensions: "dyn.preventExtensions", isExtensible: "dyn.isExtensible",
|
||||
seal: "dyn.seal", isSealed: "dyn.isSealed",
|
||||
} as const;
|
||||
const fn = operation[member as keyof typeof operation];
|
||||
return { kind: "libCall", fn, args: [value], type: member.startsWith("is") ? BOOL : DYN, loc: locOf(call) };
|
||||
}
|
||||
}
|
||||
// Object.freeze: on a FRESH literal (object or array) the result IS
|
||||
// the argument — no alias exists, so the frozen bit is unobservable
|
||||
// (writes through the Readonly<T> result are compile errors, and no
|
||||
@@ -9377,9 +9465,11 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
const isDyn = probed?.type.kind === "dyn";
|
||||
// Unit-typed arguments (Object.keys(null)) ride the same runtime
|
||||
// walk: it throws Node's catchable TypeError.
|
||||
const isUnit = probed !== null && probed !== undefined && isUnitType(probed.type);
|
||||
const isFunction = probed?.type.kind === "func" && lowerer.dynConvertible(probed.type);
|
||||
if (isDyn || isUnit || isFunction) {
|
||||
const isPrimitive = probed !== null && probed !== undefined &&
|
||||
(isUnitType(probed.type) || probed.type.kind === "string" ||
|
||||
probed.type.kind === "f64" || probed.type.kind === "bool");
|
||||
if (isDyn || isPrimitive || isFunction) {
|
||||
const fn = member === "keys" ? "dyn.objKeys" : member === "values" ? "dyn.objValues" : "dyn.objEntries";
|
||||
let v = lowerer.lowerExpr(argNode);
|
||||
if (v.type.kind !== "dyn") v = { kind: "dynFrom", value: v, type: DYN, loc: locOf(call) };
|
||||
|
||||
@@ -4696,6 +4696,17 @@ export function lowerOptionalNumber(
|
||||
}
|
||||
}
|
||||
if (receiverIr?.kind !== "array") {
|
||||
{
|
||||
const receiver = tryLowerExpression(lowerer, expr.expression);
|
||||
if (receiver?.type.kind === "func" &&
|
||||
canBoxFuncIntoDyn(receiver.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
|
||||
const key = lowerRecordPropertyKey(lowerer, lowerer.lowerExpr(expr.argumentExpression), expr.argumentExpression);
|
||||
if (key.type.kind === "string") {
|
||||
const boxed: IrExpr = { kind: "dynFrom", value: receiver, type: DYN, loc: locOf(expr.expression) };
|
||||
return lowerer.maybeNarrow({ kind: "dynKeyGet", key, value: boxed, type: DYN, loc: locOf(expr) }, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (receiverIr?.kind === "string") {
|
||||
// `s[i]` with a number index reads a UTF-16 code unit — charAt's
|
||||
// exact job (the UTF-16-exact runtime). Without
|
||||
|
||||
@@ -8063,7 +8063,7 @@ function isStrictDelete(node: ts.DeleteExpression): boolean {
|
||||
type: arrayOf(STRING), loc,
|
||||
};
|
||||
const loop = lowerForInOverKeys(lowerer, stmt, keys, labels, (key) => ({
|
||||
kind: "libCall", fn: "dyn.hasOwn", args: [ref, key], type: BOOL, loc,
|
||||
kind: "libCall", fn: "dyn.hasKey", args: [ref, key], type: BOOL, loc,
|
||||
}));
|
||||
return { kind: "block", body: [{ kind: "varDecl", localId: recv.id, init: receiver, loc }, loop], loc };
|
||||
}
|
||||
|
||||
@@ -632,6 +632,7 @@ function exactValueParams(...types: IrType[]): BuiltinValueParam[] {
|
||||
export const OBJECT_CALLABLE_VALUES: Record<string, BuiltinModuleFn | undefined> = {
|
||||
defineProperty: { fn: "dyn.defineProperty", params: [DYN, DYN, DYN], result: DYN, valueParams: exactValueParams(DYN, DYN, DYN) },
|
||||
getOwnPropertyDescriptor: { fn: "dyn.getOwnPropertyDescriptor", params: [DYN, DYN], result: DYN, valueParams: exactValueParams(DYN, DYN) },
|
||||
getOwnPropertyDescriptors: { fn: "dyn.getOwnPropertyDescriptors", params: [DYN], result: DYN, valueParams: exactValueParams(DYN) },
|
||||
defineProperties: { fn: "dyn.defineProps", params: [DYN, DYN], result: DYN, valueParams: exactValueParams(DYN, DYN) },
|
||||
keys: { fn: "dyn.objKeys", params: [DYN], result: DYN, valueParams: exactValueParams(DYN) },
|
||||
values: { fn: "dyn.objValues", params: [DYN], result: DYN, valueParams: exactValueParams(DYN) },
|
||||
|
||||
@@ -3953,6 +3953,7 @@ export type IrLibFn =
|
||||
| "dyn.objKeys"
|
||||
| "dyn.forInKeys"
|
||||
| "dyn.hasOwn"
|
||||
| "dyn.propertyIsEnumerable"
|
||||
| "dyn.assign"
|
||||
| "dyn.copyDataProperties"
|
||||
/** Variadic Object.assign over CHECKED-DYNAMIC targets (`Object.assign(
|
||||
@@ -3982,6 +3983,16 @@ export type IrLibFn =
|
||||
* and deepStrictEqual's prototype gate. Never throws. Static builds
|
||||
* only; --dynamic routes Object.create through the engine instead. */
|
||||
| "dyn.objCreateNullProto"
|
||||
| "dyn.objCreate"
|
||||
| "dyn.objCreateWithProperties"
|
||||
| "dyn.getPrototype"
|
||||
| "dyn.setPrototype"
|
||||
| "dyn.getOwnPropertyNames"
|
||||
| "dyn.getOwnPropertyDescriptors"
|
||||
| "dyn.preventExtensions"
|
||||
| "dyn.isExtensible"
|
||||
| "dyn.seal"
|
||||
| "dyn.isSealed"
|
||||
| "dyn.objValues"
|
||||
| "dyn.objEntries"
|
||||
/** structuredClone over the checked-dynamic tree (scr_json.c): the JSON-safe subset plus
|
||||
@@ -6871,7 +6882,9 @@ export function moduleUsesDynInvoke(mod: IrModule): boolean {
|
||||
}
|
||||
const node = v as { kind?: unknown; fn?: unknown };
|
||||
if (node.kind === "dynInvoke" || (node.kind === "libCall" &&
|
||||
(node.fn === "dyn.defineProps" || node.fn === "dyn.arrayProtoCall"))) {
|
||||
(node.fn === "dyn.defineProps" || node.fn === "dyn.defineProperty" ||
|
||||
node.fn === "dyn.objCreateWithProperties" ||
|
||||
node.fn === "dyn.arrayProtoCall"))) {
|
||||
found = true;
|
||||
return;
|
||||
}
|
||||
@@ -7938,6 +7951,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"dyn.objKeys",
|
||||
"dyn.forInKeys",
|
||||
"dyn.hasOwn",
|
||||
"dyn.propertyIsEnumerable",
|
||||
"dyn.assign",
|
||||
"dyn.copyDataProperties",
|
||||
// variadic Object.assign: spread flattening throws V8's spread-call
|
||||
@@ -7945,6 +7959,16 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"dyn.packPushSpread",
|
||||
"dyn.packPushSpreadIter",
|
||||
"dyn.assignAll",
|
||||
"dyn.objCreate",
|
||||
"dyn.objCreateWithProperties",
|
||||
"dyn.getPrototype",
|
||||
"dyn.setPrototype",
|
||||
"dyn.getOwnPropertyNames",
|
||||
"dyn.getOwnPropertyDescriptors",
|
||||
"dyn.preventExtensions",
|
||||
"dyn.isExtensible",
|
||||
"dyn.seal",
|
||||
"dyn.isSealed",
|
||||
"dyn.objValues",
|
||||
"dyn.objEntries",
|
||||
"error.domClone",
|
||||
|
||||
@@ -1146,6 +1146,7 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
"dyn.objKeys": { argTypes: [DYN], result: DYN },
|
||||
"dyn.forInKeys": { argTypes: [DYN], result: DYN },
|
||||
"dyn.hasOwn": { argTypes: [DYN, STRING], result: BOOL },
|
||||
"dyn.propertyIsEnumerable": { argTypes: [DYN, STRING], result: BOOL },
|
||||
"dyn.assign": { argTypes: [DYN, DYN], result: DYN },
|
||||
"dyn.copyDataProperties": { argTypes: [DYN, DYN], result: DYN },
|
||||
"dyn.packPush": { argTypes: [DYN, DYN], result: VOID },
|
||||
@@ -1153,6 +1154,16 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
"dyn.packPushSpreadIter": { argTypes: [DYN, DYN], result: VOID },
|
||||
"dyn.assignAll": { argTypes: [DYN, DYN], result: DYN },
|
||||
"dyn.objCreateNullProto": { argTypes: [], result: DYN },
|
||||
"dyn.objCreate": { argTypes: [DYN], result: DYN },
|
||||
"dyn.objCreateWithProperties": { argTypes: [DYN, DYN], result: DYN },
|
||||
"dyn.getPrototype": { argTypes: [DYN], result: DYN },
|
||||
"dyn.setPrototype": { argTypes: [DYN, DYN], result: DYN },
|
||||
"dyn.getOwnPropertyNames": { argTypes: [DYN], result: DYN },
|
||||
"dyn.getOwnPropertyDescriptors": { argTypes: [DYN], result: DYN },
|
||||
"dyn.preventExtensions": { argTypes: [DYN], result: DYN },
|
||||
"dyn.isExtensible": { argTypes: [DYN], result: BOOL },
|
||||
"dyn.seal": { argTypes: [DYN], result: DYN },
|
||||
"dyn.isSealed": { argTypes: [DYN], result: BOOL },
|
||||
"dyn.objValues": { argTypes: [DYN], result: DYN },
|
||||
"dyn.objEntries": { argTypes: [DYN], result: DYN },
|
||||
"dyn.structuredClone": { argTypes: [DYN, DYN], result: DYN },
|
||||
|
||||
@@ -9492,12 +9492,6 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/diagnostics/expressions.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/expressions.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/diagnostics/fetch-static.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/fetch-static.ts"
|
||||
@@ -11463,6 +11457,126 @@
|
||||
"<repo>/tests/corpus/typed-rest-filtered-return.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/class-accessor-returned-function.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/class-accessor-returned-function.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/class-bracket-method-calls.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/class-bracket-method-calls.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-borrowed-find-last.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-borrowed-find-last.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-borrowed-slice.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-borrowed-slice.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-copying-methods.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-copying-methods.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-fill-copywithin.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-fill-copywithin.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-frozen-noops.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-frozen-noops.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-index-boundaries.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-index-boundaries.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-integrity.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-integrity.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-length.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-length.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-named-properties.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-named-properties.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-reduce-find-last.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-reduce-find-last.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-search-from-index.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-search-from-index.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-splice-coercion.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-splice-coercion.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-array-splice-flat.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-array-splice-flat.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-object-descriptors.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-object-descriptors.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-object-for-in-minimal.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-object-for-in-minimal.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-object-property-attributes.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-object-property-attributes.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-object-prototype.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-object-prototype.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/primitive-and-function-descriptors.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/primitive-and-function-descriptors.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,10 +545,16 @@ static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
|
||||
for (size_t i = 0; i < a->v.arr.len; i++) {
|
||||
if (!scr_assert_dyn_deep_eq(a->v.arr.items[i], b->v.arr.items[i])) return false;
|
||||
}
|
||||
if (a->v.arr.properties || b->v.arr.properties) {
|
||||
if (!a->v.arr.properties || !b->v.arr.properties) {
|
||||
const ScrDyn *table = a->v.arr.properties ? a->v.arr.properties : b->v.arr.properties;
|
||||
if (table->v.obj.len != 0) return false;
|
||||
} else if (!scr_assert_dyn_deep_eq(a->v.arr.properties, b->v.arr.properties)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SCR_DYN_OBJ: {
|
||||
if (a->null_proto != b->null_proto) return false; /* the prototype gate */
|
||||
if (a->null_proto != b->null_proto || a->prototype != b->prototype) return false;
|
||||
if (a->v.obj.len != b->v.obj.len) return false;
|
||||
for (size_t i = 0; i < a->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &a->v.obj.entries[i];
|
||||
|
||||
@@ -165,23 +165,14 @@ static void dyn_throw_unsupported(const char *proto, const char *method) {
|
||||
scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b));
|
||||
}
|
||||
|
||||
/* ToIntegerOrInfinity over an OPTIONAL index argument: missing/undefined
|
||||
* answers dflt; a NUM truncates toward zero (NaN -> 0, like JS); any
|
||||
* other kind throws the loud fence (Node would ToNumber-coerce — a
|
||||
* documented gap, never a silent misread). */
|
||||
/* ToIntegerOrInfinity over an optional index argument. Missing and
|
||||
* undefined use the operation's default; present values run ToNumber once. */
|
||||
static double dyn_index_arg(ScrDyn *const *args, size_t argc, size_t i, double dflt, const char *what) {
|
||||
(void)what;
|
||||
if (i >= argc || args[i]->kind == SCR_DYN_UNDEF) return dflt;
|
||||
if (args[i]->kind == SCR_DYN_NUM) {
|
||||
double n = args[i]->v.num;
|
||||
if (n != n) return 0;
|
||||
return trunc(n);
|
||||
}
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, what);
|
||||
scr_jb_puts(&b, ": non-number index arguments on a dynamic receiver are not supported yet");
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
||||
return 0;
|
||||
double number;
|
||||
if (!scr_dyn_number_coerce_js(args[i], &number)) return 0;
|
||||
return isnan(number) ? 0 : trunc(number);
|
||||
}
|
||||
|
||||
static double dyn_bytes_index_arg(ScrDyn *const *args, size_t argc, size_t index, double fallback) {
|
||||
@@ -200,6 +191,13 @@ static size_t dyn_rel_index(double rel, size_t len) {
|
||||
return rel > (double)len ? len : (size_t)rel;
|
||||
}
|
||||
|
||||
static bool dyn_arr_length_unchanged(const ScrDyn *recv, size_t length) {
|
||||
if (recv->v.arr.len == length) return true;
|
||||
static const char message[] = "Array method after an argument changes the receiver length is not supported yet";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The array callback runner (forEach/map/filter/some/every/find/
|
||||
* findIndex): calls cb(item, i, recv) per element through the boxed
|
||||
* thunk. Returns the owned result or NULL with the exception pending. */
|
||||
@@ -269,7 +267,13 @@ static ScrDyn *dyn_borrow_get(ScrDyn *recv, const ScrStr *key) {
|
||||
return scr_dyn_new_num(length);
|
||||
}
|
||||
size_t index;
|
||||
if (!dyn_borrow_index(key, &index)) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if (!dyn_borrow_index(key, &index)) {
|
||||
if (recv->kind == SCR_DYN_ARR && recv->v.arr.properties)
|
||||
return scr_dyn_arr_named_get(recv, key);
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
if (recv->kind == SCR_DYN_ARR && index >= recv->v.arr.len && recv->v.arr.properties)
|
||||
return scr_dyn_arr_named_get(recv, key);
|
||||
if (recv->kind == SCR_DYN_ARR) return scr_dyn_arr_at(recv, (double)index);
|
||||
if (recv->kind == SCR_DYN_BYTES) {
|
||||
return index < recv->v.bytes->len
|
||||
@@ -359,7 +363,7 @@ static ScrDyn *dyn_borrow_item(ScrDyn *recv, size_t index) {
|
||||
|
||||
ScrDyn *scr_dyn_array_proto_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
if (recv->kind == SCR_DYN_NULL || recv->kind == SCR_DYN_UNDEF) {
|
||||
static const char msg[] = "Array.prototype method called on null or undefined";
|
||||
static const char msg[] = "Cannot convert undefined or null to object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
@@ -377,6 +381,8 @@ ScrDyn *scr_dyn_array_proto_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
ScrDyn *needle = argc ? argv[0] : scr_dyn_undefined();
|
||||
double from = last_index_of ? (double)len - 1 : 0;
|
||||
if (argc > 1 && !dyn_borrow_number(argv[1], &from)) return NULL;
|
||||
if (isnan(from)) from = 0;
|
||||
else from = trunc(from);
|
||||
double first = from < 0 ? (double)len + from : from;
|
||||
if (last_index_of) {
|
||||
if (from >= 0) first = fmin(from, (double)len - 1);
|
||||
@@ -416,6 +422,41 @@ ScrDyn *scr_dyn_array_proto_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
? dyn_borrow_item(recv, (size_t)index)
|
||||
: scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
if (dyn_borrow_is(method, "slice")) {
|
||||
if (recv->kind == SCR_DYN_PROXY) {
|
||||
scr_dyn_proxy_unsupported("Array.prototype.slice");
|
||||
return NULL;
|
||||
}
|
||||
double startD = 0;
|
||||
double endD = (double)len;
|
||||
if (argc && !dyn_borrow_number(argv[0], &startD)) return NULL;
|
||||
if (argc > 1 && argv[1]->kind != SCR_DYN_UNDEF && !dyn_borrow_number(argv[1], &endD)) return NULL;
|
||||
if (isnan(startD)) startD = 0;
|
||||
if (isnan(endD)) endD = 0;
|
||||
size_t start = dyn_rel_index(trunc(startD), len);
|
||||
size_t end = dyn_rel_index(trunc(endD), len);
|
||||
if (end > start && end - start > 1000000) {
|
||||
static const char message[] = "Array.prototype.slice exceeds the native iteration limit";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (size_t i = start; i < end; i++) {
|
||||
if (!dyn_borrow_iteration(i - start)) { scr_dyn_release(out); return NULL; }
|
||||
bool present = dyn_borrow_present(recv, i);
|
||||
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
|
||||
if (!present) {
|
||||
static const char message[] = "Array.prototype.slice over sparse array-like values is not supported yet";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
scr_dyn_release(out);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *item = dyn_borrow_item(recv, i);
|
||||
if (!item) { scr_dyn_release(out); return NULL; }
|
||||
scr_dyn_arr_push(out, item);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool each = dyn_borrow_is(method, "forEach");
|
||||
bool map = dyn_borrow_is(method, "map");
|
||||
@@ -424,9 +465,11 @@ ScrDyn *scr_dyn_array_proto_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
bool every = dyn_borrow_is(method, "every");
|
||||
bool find = dyn_borrow_is(method, "find");
|
||||
bool find_index = dyn_borrow_is(method, "findIndex");
|
||||
bool find_last = dyn_borrow_is(method, "findLast");
|
||||
bool find_last_index = dyn_borrow_is(method, "findLastIndex");
|
||||
bool reduce = dyn_borrow_is(method, "reduce");
|
||||
bool reduce_right = dyn_borrow_is(method, "reduceRight");
|
||||
if (!(each || map || filter || some || every || find || find_index || reduce || reduce_right)) {
|
||||
if (!(each || map || filter || some || every || find || find_index || find_last || find_last_index || reduce || reduce_right)) {
|
||||
static const char msg[] = "Array.prototype method is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
@@ -461,7 +504,8 @@ ScrDyn *scr_dyn_array_proto_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
ScrDyn *this_arg = argc > 1 ? argv[1] : scr_dyn_undefined();
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (len > 1000000 && !dyn_borrow_iteration(i)) { scr_dyn_release(out); return NULL; }
|
||||
bool present = find || find_index || dyn_borrow_present(recv, i);
|
||||
size_t index = find_last || find_last_index ? len - 1 - i : i;
|
||||
bool present = find || find_index || find_last || find_last_index || dyn_borrow_present(recv, index);
|
||||
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
|
||||
if (!present) {
|
||||
if (map) {
|
||||
@@ -472,10 +516,10 @@ ScrDyn *scr_dyn_array_proto_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ScrDyn *item = dyn_borrow_item(recv, i);
|
||||
ScrDyn *item = dyn_borrow_item(recv, index);
|
||||
if (!item) { scr_dyn_release(out); return NULL; }
|
||||
scr_dyn_this_push_dyn(this_arg);
|
||||
ScrDyn *result = dyn_call_cb(cb, item, i, recv);
|
||||
ScrDyn *result = dyn_call_cb(cb, item, index, recv);
|
||||
scr_dyn_this_pop();
|
||||
if (!result) { scr_dyn_release(item); scr_dyn_release(out); return NULL; }
|
||||
if (map) {
|
||||
@@ -486,16 +530,16 @@ ScrDyn *scr_dyn_array_proto_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
if (filter && truthy) scr_dyn_arr_push(out, scr_dyn_retain(item));
|
||||
if (some && truthy) { scr_dyn_release(item); return scr_dyn_new_bool(true); }
|
||||
if (every && !truthy) { scr_dyn_release(item); return scr_dyn_new_bool(false); }
|
||||
if (find && truthy) return item;
|
||||
if (find_index && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)i); }
|
||||
if ((find || find_last) && truthy) return item;
|
||||
if ((find_index || find_last_index) && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)index); }
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (out) return out;
|
||||
if (some) return scr_dyn_new_bool(false);
|
||||
if (every) return scr_dyn_new_bool(true);
|
||||
if (find) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if (find_index) return scr_dyn_new_num(-1);
|
||||
if (find || find_last) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if (find_index || find_last_index) return scr_dyn_new_num(-1);
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
@@ -591,18 +635,39 @@ static bool dyn_arr_sort(ScrDyn *recv, ScrDyn *cmp) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool dyn_arr_flatten(ScrDyn *out, const ScrDyn *source, double depth, size_t *visits) {
|
||||
for (size_t i = 0; i < source->v.arr.len; i++) {
|
||||
if (++*visits > 1000000) {
|
||||
static const char message[] = "Array.prototype.flat exceeds the native iteration limit";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return false;
|
||||
}
|
||||
const ScrDyn *item = source->v.arr.items[i];
|
||||
if (depth > 0 && item->kind == SCR_DYN_ARR) {
|
||||
if (!dyn_arr_flatten(out, item, depth - 1, visits)) return false;
|
||||
} else {
|
||||
scr_dyn_arr_push(out, scr_dyn_retain((ScrDyn *)item));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dyn_arr_reversed_copy(ScrDyn *out, const ScrDyn *source) {
|
||||
for (size_t i = source->v.arr.len; i > 0; i--)
|
||||
scr_dyn_arr_push(out, scr_dyn_retain(source->v.arr.items[i - 1]));
|
||||
}
|
||||
|
||||
/* Names each prototype declares BEYOND what's implemented here — these
|
||||
* fence loudly instead of mis-answering "is not a function". */
|
||||
static bool dyn_arr_proto_unimpl(const char *m) {
|
||||
static const char *names[] = { "splice", "reduce", "reduceRight", "flat",
|
||||
"fill", "copyWithin", "keys", "values", "entries", "toReversed", "toSorted", "toSpliced",
|
||||
"with", "toString", "toLocaleString", NULL };
|
||||
static const char *names[] = { "keys", "values", "entries",
|
||||
"toString", "toLocaleString", NULL };
|
||||
for (size_t i = 0; names[i]; i++) if (dyn_name_is(m, names[i])) return true;
|
||||
return false;
|
||||
}
|
||||
static bool dyn_arr_proto_mutates(const char *m) {
|
||||
static const char *names[] = {
|
||||
"push", "pop", "shift", "unshift", "reverse", "sort", NULL
|
||||
"push", "pop", "shift", "unshift", "reverse", "sort", "fill", "copyWithin", "splice", NULL
|
||||
};
|
||||
for (size_t i = 0; names[i]; i++) if (dyn_name_is(m, names[i])) return true;
|
||||
return false;
|
||||
@@ -695,10 +760,10 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
return result;
|
||||
}
|
||||
|
||||
/* OBJ: the own member calls (own properties shadow prototypes in JS
|
||||
* too); anything else is Node's is-not-a-function. */
|
||||
/* OBJ method calls follow the native prototype chain while binding the
|
||||
* original receiver as `this`. */
|
||||
if (recv->kind == SCR_DYN_OBJ) {
|
||||
ScrDyn *m = scr_dyn_obj_get(recv, method, strlen(method));
|
||||
ScrDyn *m = scr_dyn_obj_read(recv, method, strlen(method));
|
||||
if (m && (m->kind == SCR_DYN_FUNC ||
|
||||
/* a WRAPPED engine function stored as a dyn member: the
|
||||
* routed call (scr_dyn_call's JSVAL arm) runs it. */
|
||||
@@ -708,8 +773,10 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
scr_dyn_this_push_dyn(recv);
|
||||
ScrDyn *r = scr_dyn_call(m, args, argc, what);
|
||||
scr_dyn_this_pop();
|
||||
scr_dyn_release(m);
|
||||
return r;
|
||||
}
|
||||
scr_dyn_release(m);
|
||||
dyn_throw_not_fn(what);
|
||||
return NULL;
|
||||
}
|
||||
@@ -803,6 +870,63 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
|
||||
if (recv->kind == SCR_DYN_ARR) {
|
||||
size_t len = recv->v.arr.len;
|
||||
size_t splice_start = 0;
|
||||
size_t splice_removed = 0;
|
||||
if (dyn_arr_proto_mutates(method)) {
|
||||
bool grows = dyn_name_is(method, "push") && argc > 0;
|
||||
grows = grows || (dyn_name_is(method, "unshift") && argc > 0);
|
||||
bool shrinks = (dyn_name_is(method, "pop") || dyn_name_is(method, "shift")) && len > 0;
|
||||
if (dyn_name_is(method, "splice")) {
|
||||
double startD = dyn_index_arg(args, argc, 0, 0, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
splice_start = dyn_rel_index(startD, len);
|
||||
splice_removed = len - splice_start;
|
||||
if (argc > 1) {
|
||||
double countD = dyn_index_arg(args, argc, 1, 0, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
splice_removed = countD <= 0 ? 0 : countD >= (double)splice_removed ? splice_removed : (size_t)countD;
|
||||
}
|
||||
if (recv->v.arr.len != len) {
|
||||
static const char message[] = "Array.prototype.splice after an argument changes the receiver length is not supported yet";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return NULL;
|
||||
}
|
||||
size_t inserted = argc > 2 ? argc - 2 : 0;
|
||||
if (inserted > splice_removed && inserted - splice_removed > 1000000) {
|
||||
static const char message[] = "Array.prototype.splice exceeds the native array growth limit";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return NULL;
|
||||
}
|
||||
grows = grows || inserted > splice_removed;
|
||||
shrinks = shrinks || splice_removed > inserted;
|
||||
if (splice_removed > inserted && recv->v.arr.sealed && !recv->v.arr.frozen) {
|
||||
for (size_t i = splice_start; i < len - splice_removed + inserted; i++) {
|
||||
size_t source = i < splice_start + inserted ? i : i + splice_removed - inserted;
|
||||
ScrDyn *replacement = i < splice_start + inserted ? args[i - splice_start + 2] : recv->v.arr.items[source];
|
||||
ScrDyn *old = recv->v.arr.items[i];
|
||||
recv->v.arr.items[i] = scr_dyn_retain(replacement);
|
||||
scr_dyn_release(old);
|
||||
}
|
||||
static const char message[] = "Cannot delete non-configurable property";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (recv->v.arr.frozen && (dyn_name_is(method, "push") ||
|
||||
dyn_name_is(method, "pop") || dyn_name_is(method, "shift") ||
|
||||
dyn_name_is(method, "unshift") || dyn_name_is(method, "splice"))) {
|
||||
static const char message[] = "Cannot assign to read only property 'length'";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
if ((grows && recv->non_extensible) || (shrinks && recv->v.arr.sealed && !dyn_name_is(method, "shift")) ||
|
||||
(recv->v.arr.frozen && ((dyn_name_is(method, "sort") && len > 1) ||
|
||||
(dyn_name_is(method, "reverse") && len > 1)))) {
|
||||
static const char message[] = "Cannot modify a sealed or frozen array";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (dyn_name_is(method, "push")) {
|
||||
for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(recv, scr_dyn_retain(args[i]));
|
||||
return scr_dyn_new_num((double)recv->v.arr.len);
|
||||
@@ -813,26 +937,68 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
}
|
||||
if (dyn_name_is(method, "shift")) {
|
||||
if (len == 0) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if (recv->v.arr.sealed) {
|
||||
if (recv->v.arr.frozen) {
|
||||
static const char message[] = "Cannot assign to read only property";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
for (size_t i = 1; i < len; i++) {
|
||||
scr_dyn_release(recv->v.arr.items[i - 1]);
|
||||
recv->v.arr.items[i - 1] = scr_dyn_retain(recv->v.arr.items[i]);
|
||||
}
|
||||
static const char message[] = "Cannot delete non-configurable property";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *first = recv->v.arr.items[0];
|
||||
memmove(recv->v.arr.items, recv->v.arr.items + 1, (len - 1) * sizeof(ScrDyn *));
|
||||
recv->v.arr.len = len - 1;
|
||||
return first; /* ownership moves out */
|
||||
}
|
||||
if (dyn_name_is(method, "unshift")) {
|
||||
/* Append first (the push path grows capacity and takes the +1s),
|
||||
* then rotate: the old block moves up and the SAME retained
|
||||
* pointers land at the front (args[] still names them, so the
|
||||
* memmove clobbering the appended slots loses nothing). */
|
||||
for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(recv, scr_dyn_retain(args[i]));
|
||||
/* Grow the backing store before shifting the old elements. The
|
||||
* appended placeholders are immortal, so overwriting them does not
|
||||
* abandon retained argument references. */
|
||||
for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(recv, scr_dyn_retain(scr_dyn_undefined()));
|
||||
memmove(recv->v.arr.items + argc, recv->v.arr.items, len * sizeof(ScrDyn *));
|
||||
for (size_t i = 0; i < argc; i++) recv->v.arr.items[i] = args[i];
|
||||
for (size_t i = 0; i < argc; i++) recv->v.arr.items[i] = scr_dyn_retain(args[i]);
|
||||
return scr_dyn_new_num((double)recv->v.arr.len);
|
||||
}
|
||||
if (dyn_name_is(method, "splice")) {
|
||||
size_t start = splice_start;
|
||||
size_t removed = splice_removed;
|
||||
size_t inserted = argc > 2 ? argc - 2 : 0;
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (size_t i = start; i < start + removed; i++)
|
||||
scr_dyn_arr_push(out, scr_dyn_retain(recv->v.arr.items[i]));
|
||||
size_t next_len = len - removed + inserted;
|
||||
if (next_len > recv->v.arr.cap) {
|
||||
size_t capacity = recv->v.arr.cap ? recv->v.arr.cap : 4;
|
||||
while (capacity < next_len) {
|
||||
if (capacity > SIZE_MAX / 2) scr_trap("scriptc: array capacity overflow\n");
|
||||
capacity *= 2;
|
||||
}
|
||||
ScrDyn **items = realloc(recv->v.arr.items, capacity * sizeof *items);
|
||||
if (!items) scr_trap("scriptc: out of memory\n");
|
||||
recv->v.arr.items = items;
|
||||
recv->v.arr.cap = capacity;
|
||||
}
|
||||
for (size_t i = start; i < start + removed; i++) scr_dyn_release(recv->v.arr.items[i]);
|
||||
memmove(recv->v.arr.items + start + inserted,
|
||||
recv->v.arr.items + start + removed,
|
||||
(len - start - removed) * sizeof(ScrDyn *));
|
||||
for (size_t i = 0; i < inserted; i++)
|
||||
recv->v.arr.items[start + i] = scr_dyn_retain(args[i + 2]);
|
||||
recv->v.arr.len = next_len;
|
||||
return out;
|
||||
}
|
||||
if (dyn_name_is(method, "slice")) {
|
||||
double startD = dyn_index_arg(args, argc, 0, 0, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
double endD = dyn_index_arg(args, argc, 1, (double)len, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
|
||||
size_t start = dyn_rel_index(startD, len);
|
||||
size_t end = dyn_rel_index(endD, len);
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
@@ -842,20 +1008,32 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
if (dyn_name_is(method, "at")) {
|
||||
double iD = dyn_index_arg(args, argc, 0, 0, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
|
||||
double idx = iD < 0 ? (double)len + iD : iD;
|
||||
if (idx < 0 || idx >= (double)len) return scr_dyn_retain(scr_dyn_undefined());
|
||||
return scr_dyn_retain(recv->v.arr.items[(size_t)idx]);
|
||||
}
|
||||
if (dyn_name_is(method, "indexOf") || dyn_name_is(method, "lastIndexOf") ||
|
||||
dyn_name_is(method, "includes")) {
|
||||
if (len == 0) return dyn_name_is(method, "includes") ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1);
|
||||
ScrDyn *needle = argc > 0 ? args[0] : scr_dyn_undefined();
|
||||
double from = dyn_name_is(method, "lastIndexOf") ? (double)len - 1 : 0;
|
||||
if (argc > 1) {
|
||||
from = dyn_index_arg(args, argc, 1, from, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
}
|
||||
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
|
||||
if (dyn_name_is(method, "lastIndexOf")) {
|
||||
for (size_t i = len; i > 0; i--) {
|
||||
double first = from < 0 ? (double)len + from : from;
|
||||
if (first >= (double)len) first = (double)len - 1;
|
||||
for (size_t i = first < 0 ? 0 : (size_t)first + 1; i > 0; i--) {
|
||||
if (scr_dyn_strict_eq(recv->v.arr.items[i - 1], needle)) return scr_dyn_new_num((double)(i - 1));
|
||||
}
|
||||
return scr_dyn_new_num(-1);
|
||||
}
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
double first = from < 0 ? (double)len + from : from;
|
||||
if (first < 0) first = 0;
|
||||
for (size_t i = first >= (double)len ? len : (size_t)first; i < len; i++) {
|
||||
const ScrDyn *item = recv->v.arr.items[i];
|
||||
bool nan_match = dyn_name_is(method, "includes") &&
|
||||
item->kind == SCR_DYN_NUM && needle->kind == SCR_DYN_NUM &&
|
||||
@@ -866,6 +1044,28 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
}
|
||||
return dyn_name_is(method, "includes") ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1);
|
||||
}
|
||||
if (dyn_name_is(method, "findLast") || dyn_name_is(method, "findLastIndex")) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t i = len; i > 0; i--) {
|
||||
size_t index = i - 1;
|
||||
ScrDyn *item = index < recv->v.arr.len
|
||||
? scr_dyn_retain(recv->v.arr.items[index])
|
||||
: scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *result = dyn_call_cb(args[0], item, index, visible_recv);
|
||||
if (!result) { scr_dyn_release(item); return NULL; }
|
||||
bool matched = scr_dyn_truthy(result);
|
||||
scr_dyn_release(result);
|
||||
if (matched) {
|
||||
if (dyn_name_is(method, "findLast")) return item;
|
||||
scr_dyn_release(item);
|
||||
return scr_dyn_new_num((double)index);
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
return dyn_name_is(method, "findLast")
|
||||
? scr_dyn_retain(scr_dyn_undefined()) : scr_dyn_new_num(-1);
|
||||
}
|
||||
if (dyn_name_is(method, "join")) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
@@ -897,6 +1097,18 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (dyn_name_is(method, "flat")) {
|
||||
double depth = dyn_index_arg(args, argc, 0, 1, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
size_t visits = 0;
|
||||
if (!dyn_arr_flatten(out, recv, depth < 0 ? 0 : depth, &visits)) {
|
||||
scr_dyn_release(out);
|
||||
return NULL;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (dyn_name_is(method, "reverse")) {
|
||||
for (size_t i = 0; i < len / 2; i++) {
|
||||
ScrDyn *tmp = recv->v.arr.items[i];
|
||||
@@ -905,6 +1117,98 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
}
|
||||
return scr_dyn_retain(recv);
|
||||
}
|
||||
if (dyn_name_is(method, "toReversed")) {
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
dyn_arr_reversed_copy(out, recv);
|
||||
return out;
|
||||
}
|
||||
if (dyn_name_is(method, "with")) {
|
||||
double indexD = dyn_index_arg(args, argc, 0, 0, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
|
||||
double index = indexD < 0 ? (double)len + indexD : indexD;
|
||||
if (index < 0 || index >= (double)len) {
|
||||
static const char message[] = "Invalid index";
|
||||
scr_throw_error_msg(SCR_ERR_RANGE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *replacement = argc > 1 ? args[1] : scr_dyn_undefined();
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (size_t i = 0; i < len; i++)
|
||||
scr_dyn_arr_push(out, scr_dyn_retain((size_t)index == i ? replacement : recv->v.arr.items[i]));
|
||||
return out;
|
||||
}
|
||||
if (dyn_name_is(method, "toSpliced")) {
|
||||
double startD = dyn_index_arg(args, argc, 0, 0, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
size_t start = dyn_rel_index(startD, len);
|
||||
size_t delete_count = len - start;
|
||||
if (argc > 1) {
|
||||
double countD = dyn_index_arg(args, argc, 1, 0, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
delete_count = countD <= 0 ? 0 : countD >= (double)delete_count ? delete_count : (size_t)countD;
|
||||
}
|
||||
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (size_t i = 0; i < start; i++) scr_dyn_arr_push(out, scr_dyn_retain(recv->v.arr.items[i]));
|
||||
for (size_t i = 2; i < argc; i++) scr_dyn_arr_push(out, scr_dyn_retain(args[i]));
|
||||
for (size_t i = start + delete_count; i < len; i++) scr_dyn_arr_push(out, scr_dyn_retain(recv->v.arr.items[i]));
|
||||
return out;
|
||||
}
|
||||
if (dyn_name_is(method, "fill")) {
|
||||
double startD = dyn_index_arg(args, argc, 1, 0, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
double endD = dyn_index_arg(args, argc, 2, (double)len, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
|
||||
size_t start = dyn_rel_index(startD, len);
|
||||
size_t end = dyn_rel_index(endD, len);
|
||||
if (recv->v.arr.frozen && start < end) {
|
||||
static const char message[] = "Cannot assign to read only array index";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *fill = argc ? args[0] : scr_dyn_undefined();
|
||||
for (size_t i = start; i < end; i++) {
|
||||
ScrDyn *old = recv->v.arr.items[i];
|
||||
recv->v.arr.items[i] = scr_dyn_retain(fill);
|
||||
scr_dyn_release(old);
|
||||
}
|
||||
return scr_dyn_retain(recv);
|
||||
}
|
||||
if (dyn_name_is(method, "copyWithin")) {
|
||||
double targetD = dyn_index_arg(args, argc, 0, 0, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
double startD = dyn_index_arg(args, argc, 1, 0, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
double endD = dyn_index_arg(args, argc, 2, (double)len, what);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
|
||||
size_t target = dyn_rel_index(targetD, len);
|
||||
size_t start = dyn_rel_index(startD, len);
|
||||
size_t end = dyn_rel_index(endD, len);
|
||||
size_t count = end > start ? end - start : 0;
|
||||
if (count > len - target) count = len - target;
|
||||
if (recv->v.arr.frozen && count > 0) {
|
||||
static const char message[] = "Cannot assign to read only array index";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
if (start < target && target < start + count) {
|
||||
for (size_t i = count; i > 0; i--) {
|
||||
ScrDyn *old = recv->v.arr.items[target + i - 1];
|
||||
recv->v.arr.items[target + i - 1] = scr_dyn_retain(recv->v.arr.items[start + i - 1]);
|
||||
scr_dyn_release(old);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
ScrDyn *old = recv->v.arr.items[target + i];
|
||||
recv->v.arr.items[target + i] = scr_dyn_retain(recv->v.arr.items[start + i]);
|
||||
scr_dyn_release(old);
|
||||
}
|
||||
}
|
||||
return scr_dyn_retain(recv);
|
||||
}
|
||||
if (dyn_name_is(method, "forEach") || dyn_name_is(method, "map") ||
|
||||
dyn_name_is(method, "filter") || dyn_name_is(method, "some") ||
|
||||
dyn_name_is(method, "every") || dyn_name_is(method, "find") ||
|
||||
@@ -920,7 +1224,9 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
* array argument then commit directly to the original static array. */
|
||||
size_t n = len;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t i = 0; i < n && i < recv->v.arr.len; i++) {
|
||||
for (size_t step = 0; step < n; step++) {
|
||||
size_t i = step;
|
||||
if (i >= recv->v.arr.len) continue;
|
||||
ScrDyn *item = scr_dyn_retain(recv->v.arr.items[i]);
|
||||
ScrDyn *r = dyn_call_cb(cb, item, i, visible_recv);
|
||||
if (!r) { scr_dyn_release(item); scr_dyn_release(out); return NULL; }
|
||||
@@ -945,6 +1251,30 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
if (dyn_name_is(method, "findIndex")) return scr_dyn_new_num(-1);
|
||||
return scr_dyn_retain(scr_dyn_undefined()); /* forEach */
|
||||
}
|
||||
if (dyn_name_is(method, "reduce") || dyn_name_is(method, "reduceRight")) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
bool backward = dyn_name_is(method, "reduceRight");
|
||||
ScrDyn *acc = argc > 1 ? scr_dyn_retain(args[1]) : NULL;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t step = 0; step < len; step++) {
|
||||
size_t i = backward ? len - 1 - step : step;
|
||||
if (i >= recv->v.arr.len) continue;
|
||||
ScrDyn *item = scr_dyn_retain(recv->v.arr.items[i]);
|
||||
if (!acc) { acc = item; continue; }
|
||||
ScrDyn *index = scr_dyn_new_num((double)i);
|
||||
ScrDyn *call_args[] = { acc, item, index, visible_recv };
|
||||
ScrDyn *next = scr_dyn_call(args[0], call_args, 4, "callback");
|
||||
scr_dyn_release(index);
|
||||
scr_dyn_release(item);
|
||||
scr_dyn_release(acc);
|
||||
if (!next) return NULL;
|
||||
acc = next;
|
||||
}
|
||||
if (acc) return acc;
|
||||
static const char message[] = "Reduce of empty array with no initial value";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
if (dyn_name_is(method, "flatMap")) {
|
||||
/* JS Array.prototype.flatMap over a dyn array: map + a depth-1
|
||||
* flatten. Native dyn-array results flatten element-by-element; a
|
||||
@@ -1007,6 +1337,21 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
if (len > 1 && !dyn_arr_sort(recv, cmp->kind == SCR_DYN_FUNC ? cmp : NULL)) return NULL;
|
||||
return scr_dyn_retain(recv);
|
||||
}
|
||||
if (dyn_name_is(method, "toSorted")) {
|
||||
ScrDyn *cmp = argc > 0 ? args[0] : scr_dyn_undefined();
|
||||
if (cmp->kind != SCR_DYN_UNDEF && cmp->kind != SCR_DYN_FUNC) {
|
||||
static const char message[] = "The comparison function must be either a function or undefined";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (size_t i = 0; i < len; i++) scr_dyn_arr_push(out, scr_dyn_retain(recv->v.arr.items[i]));
|
||||
if (len > 1 && !dyn_arr_sort(out, cmp->kind == SCR_DYN_FUNC ? cmp : NULL)) {
|
||||
scr_dyn_release(out);
|
||||
return NULL;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (dyn_arr_proto_unimpl(method)) {
|
||||
dyn_throw_unsupported("Array", method);
|
||||
return NULL;
|
||||
@@ -1112,7 +1457,7 @@ ScrDyn *scr_dyn_define_props(ScrDyn *target, ScrDyn *descs) {
|
||||
scr_dyn_isl_fence(target, "Object.defineProperties");
|
||||
if (!scr_exc_pending()) scr_dyn_isl_fence(descs, "Object.defineProperties");
|
||||
if (scr_exc_pending()) return NULL;
|
||||
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_FUNC) {
|
||||
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR) {
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, "Object.defineProperties called on non-object",
|
||||
strlen("Object.defineProperties called on non-object"));
|
||||
return NULL;
|
||||
@@ -1193,3 +1538,13 @@ fail:
|
||||
scr_dyn_release(pending);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_obj_create_with_properties(ScrDyn *prototype, ScrDyn *descriptors) {
|
||||
ScrDyn *object = scr_dyn_obj_create(prototype);
|
||||
if (!object) return NULL;
|
||||
if (descriptors->kind == SCR_DYN_UNDEF) return object;
|
||||
ScrDyn *defined = scr_dyn_define_props(object, descriptors);
|
||||
if (!defined) { scr_dyn_release(object); return NULL; }
|
||||
scr_dyn_release(defined);
|
||||
return object;
|
||||
}
|
||||
|
||||
+472
-54
@@ -335,9 +335,11 @@ static SCR_TL size_t scr_dyn_free_count;
|
||||
* capsules keep their conservative ownership edges. */
|
||||
void scr_dyn_trace_v(void *ptr, ScrTraceVisit visit, void *ctx) {
|
||||
ScrDyn *d = ptr;
|
||||
visit(d->prototype, ctx);
|
||||
switch (d->kind) {
|
||||
case SCR_DYN_ARR:
|
||||
for (size_t i = 0; i < d->v.arr.len; i++) visit(d->v.arr.items[i], ctx);
|
||||
visit(d->v.arr.properties, ctx);
|
||||
break;
|
||||
case SCR_DYN_OBJ:
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
@@ -377,10 +379,14 @@ static ScrDyn *scr_dyn_alloc(ScrDynKind kind) {
|
||||
d->kind = kind;
|
||||
d->buffer = false;
|
||||
d->null_proto = false;
|
||||
d->prototype = NULL;
|
||||
d->copied_from_native = false;
|
||||
d->non_extensible = false;
|
||||
if (kind == SCR_DYN_ARR) {
|
||||
d->v.arr.len = 0; /* cap/items preserved from the node's last life */
|
||||
d->v.arr.properties = NULL;
|
||||
d->v.arr.sealed = false;
|
||||
d->v.arr.frozen = false;
|
||||
} else if (kind == SCR_DYN_OBJ) {
|
||||
d->v.obj.len = 0; /* cap/entries preserved */
|
||||
d->v.obj.source_identity = NULL;
|
||||
@@ -395,6 +401,8 @@ static ScrDyn *scr_dyn_alloc(ScrDynKind kind) {
|
||||
if (!fresh) scr_json_oom();
|
||||
fresh->rc = 1;
|
||||
fresh->kind = kind;
|
||||
fresh->non_extensible = false;
|
||||
fresh->prototype = NULL;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_dyns++;
|
||||
#endif
|
||||
@@ -405,6 +413,7 @@ static void scr_dyn_handle_release(void *h, ScrDynHandleTag tag);
|
||||
static bool scr_dyn_to_primitive_result_is_object(const ScrDyn *d);
|
||||
|
||||
static void scr_dyn_dispose(ScrDyn *d, bool collected) {
|
||||
if (!collected) scr_dyn_release(d->prototype);
|
||||
switch (d->kind) {
|
||||
case SCR_DYN_BIGINT:
|
||||
scr_bigint_release(d->v.bigint);
|
||||
@@ -416,8 +425,10 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) {
|
||||
scr_bytes_release(d->v.bytes);
|
||||
break;
|
||||
case SCR_DYN_ARR:
|
||||
if (!collected)
|
||||
if (!collected) {
|
||||
for (size_t i = 0; i < d->v.arr.len; i++) scr_dyn_release(d->v.arr.items[i]);
|
||||
scr_dyn_release(d->v.arr.properties);
|
||||
}
|
||||
break;
|
||||
case SCR_DYN_OBJ:
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
@@ -527,6 +538,9 @@ bool scr_dyn_obj_enumerable(const ScrDyn *d, const char *key, size_t key_len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const ScrDyn *scr_dyn_property_owner(const ScrDyn *object, const char *key, size_t length);
|
||||
static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_t key_len, const ScrDyn *receiver);
|
||||
|
||||
/* Read through a typed capsule without exposing its native layout to a
|
||||
* matcher for another type. The returned member owns its reference after
|
||||
* the materialized view is released. */
|
||||
@@ -538,8 +552,8 @@ static ScrDyn *scr_dyn_discriminant(const ScrDyn *d, const ScrStr *key) {
|
||||
return out;
|
||||
}
|
||||
if (!d || d->kind != SCR_DYN_OBJ) return NULL;
|
||||
ScrDyn *value = scr_dyn_obj_get(d, key->data, key->len);
|
||||
return value ? scr_dyn_retain(value) : NULL;
|
||||
return scr_dyn_property_owner(d, key->data, key->len)
|
||||
? scr_dyn_obj_read(d, key->data, key->len) : NULL;
|
||||
}
|
||||
|
||||
bool scr_dyn_field_eq_str(const ScrDyn *d, const ScrStr *key, const ScrStr *value) {
|
||||
@@ -750,6 +764,12 @@ ScrDyn *scr_dyn_arr_at(const ScrDyn *d, double i) {
|
||||
return scr_dyn_retain(d->v.arr.items[(size_t)i]);
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_arr_named_get(const ScrDyn *d, const ScrStr *key) {
|
||||
return d->v.arr.properties
|
||||
? scr_dyn_obj_read_receiver(d->v.arr.properties, key->data, key->len, d)
|
||||
: scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
/* Takes ownership of key (malloc'd) and value. Duplicate keys: the LATER
|
||||
* value wins (like JS JSON.parse) — the old value is released and the new
|
||||
* key buffer freed (the surviving entry keeps its original, equal key). */
|
||||
@@ -757,6 +777,13 @@ static void scr_dyn_obj_put(ScrDyn *obj, char *key, size_t key_len, ScrDyn *valu
|
||||
for (size_t i = 0; i < obj->v.obj.len; i++) {
|
||||
ScrDynEntry *e = &obj->v.obj.entries[i];
|
||||
if (e->key_len == key_len && memcmp(e->key, key, key_len) == 0) {
|
||||
if (e->accessor || !e->writable) {
|
||||
free(key);
|
||||
scr_dyn_release(value);
|
||||
static const char message[] = "Cannot assign to read only property";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
ScrDyn *old = e->value;
|
||||
e->value = value;
|
||||
scr_dyn_release(old);
|
||||
@@ -764,6 +791,13 @@ static void scr_dyn_obj_put(ScrDyn *obj, char *key, size_t key_len, ScrDyn *valu
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (obj->non_extensible) {
|
||||
free(key);
|
||||
scr_dyn_release(value);
|
||||
static const char message[] = "Cannot add property, object is not extensible";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
if (obj->v.obj.len == obj->v.obj.cap) {
|
||||
size_t cap = obj->v.obj.cap ? obj->v.obj.cap * 2 : 4;
|
||||
ScrDynEntry *entries = realloc(obj->v.obj.entries, cap * sizeof *entries);
|
||||
@@ -788,7 +822,7 @@ static void scr_dyn_obj_put(ScrDyn *obj, char *key, size_t key_len, ScrDyn *valu
|
||||
/* THE undefined value: one immortal node (rc == SIZE_MAX skips every
|
||||
* retain/release and the freelists never see it). */
|
||||
ScrDyn *scr_dyn_undefined(void) {
|
||||
static ScrDyn undef = { SIZE_MAX, SCR_DYN_UNDEF, { false } };
|
||||
static ScrDyn undef = { .rc = SIZE_MAX, .kind = SCR_DYN_UNDEF };
|
||||
return &undef;
|
||||
}
|
||||
|
||||
@@ -848,6 +882,122 @@ ScrDyn *scr_dyn_new_obj_null_proto(void) {
|
||||
return d;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_obj_create(ScrDyn *prototype) {
|
||||
if (scr_dyn_class_reflection_fence(prototype)) return NULL;
|
||||
if (prototype->kind != SCR_DYN_OBJ && prototype->kind != SCR_DYN_NULL) {
|
||||
static const char message[] = "Object prototype may only be an Object or null";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *object = prototype->kind == SCR_DYN_NULL ? scr_dyn_new_obj_null_proto() : scr_dyn_new_obj();
|
||||
if (prototype->kind == SCR_DYN_OBJ) object->prototype = scr_dyn_retain(prototype);
|
||||
return object;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_get_prototype(ScrDyn *object) {
|
||||
if (scr_dyn_class_reflection_fence(object)) return NULL;
|
||||
if (object->kind == SCR_DYN_OBJ) {
|
||||
if (object->prototype) return scr_dyn_retain(object->prototype);
|
||||
if (object->null_proto) return scr_dyn_new_null();
|
||||
}
|
||||
if (object->kind == SCR_DYN_NULL || object->kind == SCR_DYN_UNDEF) {
|
||||
static const char message[] = "Cannot convert undefined or null to object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
static const char message[] = "Object.getPrototypeOf on this value is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_set_prototype(ScrDyn *object, ScrDyn *prototype) {
|
||||
if (scr_dyn_class_reflection_fence(object) || scr_dyn_class_reflection_fence(prototype)) return NULL;
|
||||
if (object->kind != SCR_DYN_OBJ ||
|
||||
(prototype->kind != SCR_DYN_OBJ && prototype->kind != SCR_DYN_NULL)) {
|
||||
static const char message[] = "Object prototype may only be an Object or null";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
for (ScrDyn *current = prototype; current && current->kind == SCR_DYN_OBJ; current = current->prototype) {
|
||||
if (current == object) {
|
||||
static const char message[] = "Cyclic __proto__ value";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (object->non_extensible && (prototype->kind == SCR_DYN_NULL ? object->prototype != NULL || !object->null_proto : object->prototype != prototype)) {
|
||||
static const char message[] = "Cannot set prototype of a non-extensible object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *replacement = prototype->kind == SCR_DYN_NULL ? NULL : scr_dyn_retain(prototype);
|
||||
scr_dyn_release(object->prototype);
|
||||
object->prototype = replacement;
|
||||
object->null_proto = replacement ? false : true;
|
||||
return scr_dyn_retain(object);
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object) {
|
||||
if (scr_dyn_class_reflection_fence(object)) return NULL;
|
||||
if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.preventExtensions"); return NULL; }
|
||||
if (object->kind == SCR_DYN_OBJ || object->kind == SCR_DYN_ARR) {
|
||||
object->non_extensible = true;
|
||||
if (object->kind == SCR_DYN_ARR && object->v.arr.properties)
|
||||
object->v.arr.properties->non_extensible = true;
|
||||
}
|
||||
return scr_dyn_retain(object);
|
||||
}
|
||||
|
||||
bool scr_dyn_is_extensible(ScrDyn *object) {
|
||||
if (scr_dyn_class_reflection_fence(object)) return false;
|
||||
if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.isExtensible"); return false; }
|
||||
return (object->kind == SCR_DYN_OBJ || object->kind == SCR_DYN_ARR) && !object->non_extensible;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_seal(ScrDyn *object) {
|
||||
if (scr_dyn_class_reflection_fence(object)) return NULL;
|
||||
if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.seal"); return NULL; }
|
||||
if (object->kind == SCR_DYN_OBJ) {
|
||||
object->non_extensible = true;
|
||||
for (size_t i = 0; i < object->v.obj.len; i++) object->v.obj.entries[i].configurable = false;
|
||||
}
|
||||
if (object->kind == SCR_DYN_ARR) {
|
||||
object->non_extensible = true;
|
||||
object->v.arr.sealed = true;
|
||||
if (object->v.arr.properties) scr_dyn_release(scr_dyn_seal(object->v.arr.properties));
|
||||
}
|
||||
return scr_dyn_retain(object);
|
||||
}
|
||||
|
||||
bool scr_dyn_is_sealed(const ScrDyn *object) {
|
||||
if (scr_dyn_class_reflection_fence(object)) return false;
|
||||
if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.isSealed"); return false; }
|
||||
if (object->kind == SCR_DYN_ARR)
|
||||
return object->non_extensible && (object->v.arr.len == 0 || object->v.arr.sealed) &&
|
||||
(!object->v.arr.properties || scr_dyn_is_sealed(object->v.arr.properties));
|
||||
if (object->kind != SCR_DYN_OBJ) return true;
|
||||
if (!object->non_extensible) return false;
|
||||
for (size_t i = 0; i < object->v.obj.len; i++) if (object->v.obj.entries[i].configurable) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const ScrDyn *scr_dyn_property_owner(const ScrDyn *object, const char *key, size_t length) {
|
||||
for (const ScrDyn *current = object; current; current = current->prototype) {
|
||||
if (current->kind != SCR_DYN_OBJ) continue;
|
||||
for (size_t i = 0; i < current->v.obj.len; i++) {
|
||||
const ScrDynEntry *entry = ¤t->v.obj.entries[i];
|
||||
if (entry->key_len == length && memcmp(entry->key, key, length) == 0) return current;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool scr_dyn_has_object_prototype(const ScrDyn *object) {
|
||||
const ScrDyn *current = object;
|
||||
while (current->prototype) current = current->prototype;
|
||||
return !current->null_proto;
|
||||
}
|
||||
|
||||
void scr_dyn_proxy_unsupported(const char *operation) {
|
||||
ScrJsonBuf msg;
|
||||
scr_jb_init(&msg);
|
||||
@@ -965,6 +1115,8 @@ bool scr_dyn_proxy_has(const ScrDyn *proxy, const ScrStr *key) {
|
||||
return has;
|
||||
}
|
||||
|
||||
static bool scr_dyn_canonical_own_index(const ScrStr *key, size_t length);
|
||||
|
||||
ScrDyn *scr_dyn_own_descriptor(const ScrDyn *value, const ScrStr *key) {
|
||||
if (scr_dyn_class_reflection_fence(value)) return NULL;
|
||||
if (value->kind == SCR_DYN_FUNC) {
|
||||
@@ -988,11 +1140,52 @@ ScrDyn *scr_dyn_own_descriptor(const ScrDyn *value, const ScrStr *key) {
|
||||
if (value->kind == SCR_DYN_ARR && key->len == 6 && memcmp(key->data, "length", 6) == 0) {
|
||||
ScrDyn *out = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(out, "value", 5, scr_dyn_new_num((double)value->v.arr.len));
|
||||
scr_dyn_obj_set(out, "writable", 8, scr_dyn_new_bool(true));
|
||||
scr_dyn_obj_set(out, "writable", 8, scr_dyn_new_bool(!value->v.arr.frozen));
|
||||
scr_dyn_obj_set(out, "enumerable", 10, scr_dyn_new_bool(false));
|
||||
scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(false));
|
||||
return out;
|
||||
}
|
||||
if (value->kind == SCR_DYN_ARR) {
|
||||
if (value->v.arr.properties &&
|
||||
scr_dyn_obj_get(value->v.arr.properties, key->data, key->len))
|
||||
return scr_dyn_own_descriptor(value->v.arr.properties, key);
|
||||
if (scr_dyn_canonical_own_index(key, value->v.arr.len)) {
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < key->len; i++) index = index * 10 + (size_t)(key->data[i] - '0');
|
||||
ScrDyn *out = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(out, "value", 5, scr_dyn_retain(value->v.arr.items[index]));
|
||||
scr_dyn_obj_set(out, "writable", 8, scr_dyn_new_bool(!value->v.arr.frozen));
|
||||
scr_dyn_obj_set(out, "enumerable", 10, scr_dyn_new_bool(true));
|
||||
scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(!value->v.arr.sealed));
|
||||
return out;
|
||||
}
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
if (value->kind == SCR_DYN_STR) {
|
||||
size_t length = (size_t)scr_str_utf16_len(value->v.str);
|
||||
bool length_key = key->len == 6 && memcmp(key->data, "length", 6) == 0;
|
||||
size_t index = 0;
|
||||
bool index_key = !length_key && scr_dyn_canonical_own_index(key, length);
|
||||
if (!length_key && !index_key) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if (index_key) {
|
||||
for (size_t i = 0; i < key->len; i++) index = index * 10 + (size_t)(key->data[i] - '0');
|
||||
}
|
||||
ScrDyn *out = scr_dyn_new_obj();
|
||||
if (length_key) {
|
||||
scr_dyn_obj_set(out, "value", 5, scr_dyn_new_num((double)length));
|
||||
} else {
|
||||
ScrStr *character = scr_str_char_at(value->v.str, (double)index);
|
||||
scr_dyn_obj_set(out, "value", 5, scr_dyn_new_str(character));
|
||||
scr_str_release(character);
|
||||
}
|
||||
scr_dyn_obj_set(out, "writable", 8, scr_dyn_new_bool(false));
|
||||
scr_dyn_obj_set(out, "enumerable", 10, scr_dyn_new_bool(index_key));
|
||||
scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(false));
|
||||
return out;
|
||||
}
|
||||
if (value->kind == SCR_DYN_BOOL || value->kind == SCR_DYN_NUM) {
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
if (value->kind == SCR_DYN_OBJ) {
|
||||
ScrDynEntry *entry = scr_dyn_entry((ScrDyn *)value, key);
|
||||
if (!entry) return scr_dyn_retain(scr_dyn_undefined());
|
||||
@@ -1945,8 +2138,8 @@ static bool scr_dyn_property_same_value(const ScrDyn *a, const ScrDyn *b) {
|
||||
static void scr_dyn_descriptor_fields_drop(ScrDyn **fields) {
|
||||
for (size_t i = 0; i < 6; i++) scr_dyn_release(fields[i]);
|
||||
}
|
||||
static bool scr_dyn_key_is_index(const char *key, size_t len, double *out);
|
||||
static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key);
|
||||
static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_t key_len, const ScrDyn *receiver);
|
||||
|
||||
/* Property tables belong to closures, not temporary checked wrappers. Seed
|
||||
* the configurable, non-writable builtin members once so redefinitions and
|
||||
@@ -2011,6 +2204,25 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor)
|
||||
scr_dyn_release(defined);
|
||||
return scr_dyn_retain(target);
|
||||
}
|
||||
if (target->kind == SCR_DYN_ARR) {
|
||||
ScrStr *name = scr_dyn_property_key(key);
|
||||
if (!name) return NULL;
|
||||
double index;
|
||||
bool special = name->len == 6 && memcmp(name->data, "length", 6) == 0;
|
||||
special = special || scr_dyn_key_is_index(name->data, name->len, &index);
|
||||
scr_str_release(name);
|
||||
if (special) {
|
||||
static const char message[] = "Object.defineProperty on an array index or length is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
if (!target->v.arr.properties) target->v.arr.properties = scr_dyn_new_obj();
|
||||
target->v.arr.properties->non_extensible = target->non_extensible;
|
||||
ScrDyn *defined = scr_dyn_define_property(target->v.arr.properties, key, descriptor);
|
||||
if (!defined) return NULL;
|
||||
scr_dyn_release(defined);
|
||||
return scr_dyn_retain(target);
|
||||
}
|
||||
if (target->kind != SCR_DYN_OBJ) {
|
||||
static const char msg[] = "Object.defineProperty called on non-object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
@@ -2140,7 +2352,7 @@ ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key) {
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_PROXY && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR) {
|
||||
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_PROXY && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR && target->kind != SCR_DYN_STR && target->kind != SCR_DYN_BOOL && target->kind != SCR_DYN_NUM) {
|
||||
static const char msg[] = "Object.getOwnPropertyDescriptor on this value is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
@@ -2733,7 +2945,7 @@ ScrStr *scr_dyn_to_string_argument(const ScrDyn *d, const ScrDyn *argument, cons
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (d->kind == SCR_DYN_OBJ && d->null_proto) {
|
||||
if (d->kind == SCR_DYN_OBJ && !scr_dyn_property_owner(d, "toString", 8) && !scr_dyn_has_object_prototype(d)) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
for (size_t i = 0; i < what->len; i++) scr_jb_putc(&b, what->data[i]);
|
||||
@@ -2802,16 +3014,18 @@ ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) {
|
||||
if (d->kind == SCR_DYN_OBJ) {
|
||||
static const char *const hint[2] = { "toString", "valueOf" };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ScrDyn *m = scr_dyn_obj_get(d, hint[i], strlen(hint[i])); /* borrowed */
|
||||
if (!m && i == 0 && !d->null_proto) {
|
||||
const ScrDyn *owner = scr_dyn_property_owner(d, hint[i], strlen(hint[i]));
|
||||
ScrDyn *m = owner ? scr_dyn_obj_read(d, hint[i], strlen(hint[i])) : NULL;
|
||||
if (!m && i == 0 && scr_dyn_has_object_prototype(d)) {
|
||||
return scr_str_new("[object Object]", 15);
|
||||
}
|
||||
if (!m || m->kind != SCR_DYN_FUNC) continue;
|
||||
if (!m || m->kind != SCR_DYN_FUNC) { scr_dyn_release(m); continue; }
|
||||
/* OrdinaryToPrimitive performs a method call, not a bare function
|
||||
* call: an own coercion hook observes the source object as `this`. */
|
||||
scr_dyn_this_push_dyn(d);
|
||||
ScrDyn *r = scr_dyn_call(m, NULL, 0, hint[i]);
|
||||
scr_dyn_this_pop();
|
||||
scr_dyn_release(m);
|
||||
if (!r) return NULL; /* the method threw — pending */
|
||||
if (scr_dyn_to_primitive_result_is_object(r)) {
|
||||
scr_dyn_release(r); /* non-primitive answer: try the next method */
|
||||
@@ -2869,23 +3083,25 @@ bool scr_dyn_number_coerce_js(const ScrDyn *d, double *out) {
|
||||
case SCR_DYN_OBJ: {
|
||||
static const char *const hint[2] = { "valueOf", "toString" };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ScrDyn *m = scr_dyn_obj_get(d, hint[i], strlen(hint[i])); /* borrowed */
|
||||
const ScrDyn *owner = scr_dyn_property_owner(d, hint[i], strlen(hint[i]));
|
||||
ScrDyn *m = owner ? scr_dyn_obj_read(d, hint[i], strlen(hint[i])) : NULL;
|
||||
if (!m) {
|
||||
if (!d->null_proto && i == 0) {
|
||||
if (scr_dyn_has_object_prototype(d) && i == 0) {
|
||||
/* Inherited Object.prototype.valueOf returns the object, so the
|
||||
* number-hint protocol advances to toString. */
|
||||
continue;
|
||||
}
|
||||
if (!d->null_proto && i == 1) {
|
||||
if (scr_dyn_has_object_prototype(d) && i == 1) {
|
||||
*out = NAN; /* Number("[object Object]") */
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (m->kind != SCR_DYN_FUNC) continue;
|
||||
if (m->kind != SCR_DYN_FUNC) { scr_dyn_release(m); continue; }
|
||||
scr_dyn_this_push_dyn(d);
|
||||
ScrDyn *r = scr_dyn_call(m, NULL, 0, hint[i]);
|
||||
scr_dyn_this_pop();
|
||||
scr_dyn_release(m);
|
||||
if (!r) return false;
|
||||
if (scr_dyn_to_primitive_result_is_object(r)) {
|
||||
scr_dyn_release(r);
|
||||
@@ -2950,9 +3166,10 @@ static ScrDyn *scr_dyn_add_primitive(const ScrDyn *d) {
|
||||
if (d->kind == SCR_DYN_OBJ) {
|
||||
static const char *const methods[] = { "valueOf", "toString" };
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
ScrDyn *method = scr_dyn_obj_get(d, methods[i], strlen(methods[i]));
|
||||
const ScrDyn *owner = scr_dyn_property_owner(d, methods[i], strlen(methods[i]));
|
||||
ScrDyn *method = owner ? scr_dyn_obj_read(d, methods[i], strlen(methods[i])) : NULL;
|
||||
if (!method) {
|
||||
if (i == 1 && !d->null_proto) {
|
||||
if (i == 1 && scr_dyn_has_object_prototype(d)) {
|
||||
ScrStr *str = scr_str_new("[object Object]", 15);
|
||||
ScrDyn *out = scr_dyn_new_str(str);
|
||||
scr_str_release(str);
|
||||
@@ -2960,8 +3177,7 @@ static ScrDyn *scr_dyn_add_primitive(const ScrDyn *d) {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (method->kind != SCR_DYN_FUNC) continue;
|
||||
scr_dyn_retain(method);
|
||||
if (method->kind != SCR_DYN_FUNC) { scr_dyn_release(method); continue; }
|
||||
scr_dyn_this_push_dyn(d);
|
||||
ScrDyn *out = scr_dyn_call(method, NULL, 0, methods[i]);
|
||||
scr_dyn_this_pop();
|
||||
@@ -3039,7 +3255,12 @@ bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key) {
|
||||
scr_dyn_release(materialized);
|
||||
return out;
|
||||
}
|
||||
if (v->kind == SCR_DYN_OBJ) return scr_dyn_obj_get(v, key->data, key->len) != NULL;
|
||||
if (v->kind == SCR_DYN_OBJ) {
|
||||
for (const ScrDyn *current = v; current && current->kind == SCR_DYN_OBJ; current = current->prototype) {
|
||||
if (scr_dyn_obj_get(current, key->data, key->len)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (v->kind == SCR_DYN_FUNC) {
|
||||
ScrDyn *descriptor = scr_dyn_own_descriptor(v, key);
|
||||
bool present = descriptor && descriptor->kind != SCR_DYN_UNDEF;
|
||||
@@ -3058,16 +3279,20 @@ bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key) {
|
||||
}
|
||||
if (v->kind == SCR_DYN_ARR) {
|
||||
if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return true;
|
||||
if (key->len == 0 || key->len > 15) return false;
|
||||
if (v->v.arr.properties && scr_dyn_obj_get(v->v.arr.properties, key->data, key->len)) return true;
|
||||
if (key->len == 0 || key->len > 10) return false;
|
||||
size_t idx = 0;
|
||||
for (size_t i = 0; i < key->len; i++) {
|
||||
char c = key->data[i];
|
||||
if (c < '0' || c > '9') return false;
|
||||
if (i > 0 && idx == 0) return false; /* a leading zero is no canonical index */
|
||||
if (idx > (4294967294ULL - (size_t)(c - '0')) / 10) return false;
|
||||
idx = idx * 10 + (size_t)(c - '0');
|
||||
}
|
||||
return idx < v->v.arr.len;
|
||||
}
|
||||
if (v->kind == SCR_DYN_STR)
|
||||
return scr_dyn_canonical_own_index(key, (size_t)scr_str_utf16_len(v->v.str));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3091,6 +3316,28 @@ static void scr_dyn_object_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value, con
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!scr_dyn_entry(recv, key)) {
|
||||
for (const ScrDyn *current = recv->prototype; current && current->kind == SCR_DYN_OBJ; current = current->prototype) {
|
||||
ScrDynEntry *inherited = scr_dyn_entry((ScrDyn *)current, key);
|
||||
if (!inherited) continue;
|
||||
if (inherited->accessor && inherited->setter) {
|
||||
ScrDyn *args[] = {value};
|
||||
ScrDyn *setter = scr_dyn_retain(inherited->setter);
|
||||
scr_dyn_this_push_dyn(receiver);
|
||||
ScrDyn *result = scr_dyn_call(setter, args, 1, "setter");
|
||||
scr_dyn_this_pop();
|
||||
scr_dyn_release(setter);
|
||||
scr_dyn_release(result);
|
||||
return;
|
||||
}
|
||||
if (inherited->accessor || !inherited->writable) {
|
||||
static const char message[] = "Cannot assign to read only property";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (recv->non_extensible && !scr_dyn_entry(recv, key)) {
|
||||
ScrJsonBuf buffer;
|
||||
scr_jb_init(&buffer);
|
||||
@@ -3127,17 +3374,63 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
|
||||
return;
|
||||
}
|
||||
if (recv->kind == SCR_DYN_ARR) {
|
||||
/* An INDEX write on a dyn array (`args[i] = v` — the variadic-rest
|
||||
* rebuild): a canonical numeric key sets/extends the element, holes
|
||||
* padding with undefined exactly like JS length growth. Non-index
|
||||
* keys keep the throw below (dyn arrays carry no expando table). */
|
||||
if (key->len == 6 && memcmp(key->data, "length", 6) == 0) {
|
||||
if (recv->v.arr.frozen) {
|
||||
static const char message[] = "Cannot assign to read only property";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
double number;
|
||||
if (!scr_dyn_number_coerce_js(value, &number)) return;
|
||||
if (!isfinite(number) || number < 0 || number > 4294967295.0 || trunc(number) != number) {
|
||||
static const char message[] = "Invalid array length";
|
||||
scr_throw_error_msg(SCR_ERR_RANGE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
size_t length = (size_t)number;
|
||||
if (length > recv->v.arr.len && recv->non_extensible) {
|
||||
static const char message[] = "Cannot add property, object is not extensible";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
if (length > recv->v.arr.len) {
|
||||
static const char message[] = "Sparse array lengths are not supported yet";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return;
|
||||
}
|
||||
if (length < recv->v.arr.len && recv->v.arr.sealed) {
|
||||
static const char message[] = "Cannot delete non-configurable property";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
while (recv->v.arr.len > length) scr_dyn_release(recv->v.arr.items[--recv->v.arr.len]);
|
||||
return;
|
||||
}
|
||||
/* Canonical indexes extend the dense portion; other names live in the
|
||||
* array's own property table. */
|
||||
size_t idx = 0;
|
||||
int is_index = key->len > 0 && !(key->len > 1 && key->data[0] == '0');
|
||||
for (size_t i = 0; is_index && i < key->len; i++) {
|
||||
if (key->data[i] < '0' || key->data[i] > '9') is_index = 0;
|
||||
else if (idx > (4294967295ULL - (size_t)(key->data[i] - '0')) / 10) is_index = 0;
|
||||
else idx = idx * 10 + (size_t)(key->data[i] - '0');
|
||||
}
|
||||
if (is_index) {
|
||||
if (is_index && idx < 4294967295ULL) {
|
||||
if (recv->v.arr.frozen && idx < recv->v.arr.len) {
|
||||
static const char message[] = "Cannot assign to read only property";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
if (recv->non_extensible && idx >= recv->v.arr.len) {
|
||||
static const char message[] = "Cannot add property, object is not extensible";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
if (idx > recv->v.arr.len + 1000000) {
|
||||
static const char message[] = "Sparse array writes are not supported yet";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return;
|
||||
}
|
||||
while (recv->v.arr.len <= idx) {
|
||||
scr_dyn_arr_push(recv, scr_dyn_retain(scr_dyn_undefined()));
|
||||
}
|
||||
@@ -3146,6 +3439,10 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
|
||||
scr_dyn_release(old);
|
||||
return;
|
||||
}
|
||||
if (!recv->v.arr.properties) recv->v.arr.properties = scr_dyn_new_obj();
|
||||
recv->v.arr.properties->non_extensible = recv->non_extensible;
|
||||
scr_dyn_key_set(recv->v.arr.properties, key, value);
|
||||
return;
|
||||
}
|
||||
if (recv->kind == SCR_DYN_HANDLE) {
|
||||
scr_dyn_handle_key_set(recv, key, value);
|
||||
@@ -3993,6 +4290,11 @@ void scr_dyn_key_delete(ScrDyn *recv, const ScrStr *key, bool strict) {
|
||||
scr_error_sync_cause(recv, key);
|
||||
return;
|
||||
}
|
||||
if (recv->kind == SCR_DYN_ARR && recv->v.arr.properties &&
|
||||
scr_dyn_obj_get(recv->v.arr.properties, key->data, key->len)) {
|
||||
scr_dyn_key_delete(recv->v.arr.properties, key, strict);
|
||||
return;
|
||||
}
|
||||
if (recv->kind == SCR_DYN_UNDEF || recv->kind == SCR_DYN_NULL) {
|
||||
const char *message = "Cannot convert undefined or null to object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message));
|
||||
@@ -4480,17 +4782,19 @@ bool scr_dyn_err_instanceof(const ScrDyn *d, double kind) {
|
||||
}
|
||||
|
||||
static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_t key_len, const ScrDyn *receiver) {
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
ScrDynEntry *entry = &d->v.obj.entries[i];
|
||||
if (entry->key_len != key_len || memcmp(entry->key, key, key_len) != 0) continue;
|
||||
if (!entry->accessor) return scr_dyn_retain(entry->value);
|
||||
if (!entry->getter) return scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *getter = scr_dyn_retain(entry->getter);
|
||||
scr_dyn_this_push_dyn(receiver);
|
||||
ScrDyn *result = scr_dyn_call(getter, NULL, 0, "getter");
|
||||
scr_dyn_this_pop();
|
||||
scr_dyn_release(getter);
|
||||
return result;
|
||||
for (const ScrDyn *current = d; current && current->kind == SCR_DYN_OBJ; current = current->prototype) {
|
||||
for (size_t i = 0; i < current->v.obj.len; i++) {
|
||||
ScrDynEntry *entry = ¤t->v.obj.entries[i];
|
||||
if (entry->key_len != key_len || memcmp(entry->key, key, key_len) != 0) continue;
|
||||
if (!entry->accessor) return scr_dyn_retain(entry->value);
|
||||
if (!entry->getter) return scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *getter = scr_dyn_retain(entry->getter);
|
||||
scr_dyn_this_push_dyn(receiver);
|
||||
ScrDyn *result = scr_dyn_call(getter, NULL, 0, "getter");
|
||||
scr_dyn_this_pop();
|
||||
scr_dyn_release(getter);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
ScrDyn *own = scr_dyn_obj_get(d, key, key_len);
|
||||
if (own) return scr_dyn_retain(own);
|
||||
@@ -4587,6 +4891,68 @@ ScrDyn *scr_dyn_obj_own_keys(const ScrDyn *v) {
|
||||
return keys;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value) {
|
||||
if (value->kind == SCR_DYN_OBJ) return scr_dyn_obj_own_keys(value);
|
||||
if (value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_STR || value->kind == SCR_DYN_BYTES) {
|
||||
ScrDyn *keys = scr_dyn_new_arr();
|
||||
size_t length = value->kind == SCR_DYN_ARR ? value->v.arr.len :
|
||||
value->kind == SCR_DYN_BYTES ? value->v.bytes->len :
|
||||
(size_t)scr_str_utf16_len(value->v.str);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
char name[24];
|
||||
int size = snprintf(name, sizeof name, "%zu", i);
|
||||
scr_dyn_arr_push(keys, scr_dyn_objwalk_key(name, (size_t)size));
|
||||
}
|
||||
if (value->kind != SCR_DYN_BYTES)
|
||||
scr_dyn_arr_push(keys, scr_dyn_objwalk_key("length", 6));
|
||||
if (value->kind == SCR_DYN_ARR && value->v.arr.properties) {
|
||||
ScrDyn *properties = scr_dyn_obj_own_keys(value->v.arr.properties);
|
||||
for (size_t i = 0; i < properties->v.arr.len; i++)
|
||||
scr_dyn_arr_push(keys, scr_dyn_retain(properties->v.arr.items[i]));
|
||||
scr_dyn_release(properties);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
if (value->kind == SCR_DYN_BOOL || value->kind == SCR_DYN_NUM)
|
||||
return scr_dyn_new_arr();
|
||||
if (value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF) {
|
||||
static const char message[] = "Cannot convert undefined or null to object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
static const char message[] = "Object.getOwnPropertyNames on this value is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_get_own_property_descriptors(ScrDyn *object) {
|
||||
if (scr_dyn_class_reflection_fence(object)) return NULL;
|
||||
if (object->kind == SCR_DYN_UNDEF || object->kind == SCR_DYN_NULL) {
|
||||
static const char message[] = "Cannot convert undefined or null to object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
if (object->kind == SCR_DYN_PROXY || object->kind == SCR_DYN_TYPED_REF ||
|
||||
object->kind == SCR_DYN_JSVAL || object->kind == SCR_DYN_HANDLE) {
|
||||
static const char message[] = "Object.getOwnPropertyDescriptors on this value is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *names = scr_dyn_get_own_property_names(object);
|
||||
if (!names) return NULL;
|
||||
ScrDyn *result = scr_dyn_new_obj();
|
||||
for (size_t i = 0; i < names->v.arr.len; i++) {
|
||||
ScrStr *name = names->v.arr.items[i]->v.str;
|
||||
ScrDyn *descriptor = scr_dyn_own_descriptor(object, name);
|
||||
if (!descriptor) { scr_dyn_release(names); scr_dyn_release(result); return NULL; }
|
||||
if (descriptor->kind != SCR_DYN_UNDEF)
|
||||
scr_dyn_obj_set(result, name->data, name->len, descriptor);
|
||||
else scr_dyn_release(descriptor);
|
||||
}
|
||||
scr_dyn_release(names);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void scr_dyn_objwalk_push(ScrDyn *out, ScrObjWalk mode, const char *key,
|
||||
size_t key_len, ScrDyn *value /* borrowed */) {
|
||||
switch (mode) {
|
||||
@@ -4748,24 +5114,30 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
|
||||
scr_dyn_arr_push(out, pair);
|
||||
}
|
||||
}
|
||||
if (v->kind == SCR_DYN_ARR && v->v.arr.properties) {
|
||||
ScrDyn *names = scr_dyn_obj_own_keys(v->v.arr.properties);
|
||||
for (size_t i = 0; i < names->v.arr.len; i++) {
|
||||
if (!scr_dyn_objwalk_entry(out, v->v.arr.properties, mode, names->v.arr.items[i]->v.str, v)) {
|
||||
scr_dyn_release(names);
|
||||
scr_dyn_release(out);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
scr_dyn_release(names);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (v->kind == SCR_DYN_STR) {
|
||||
/* JS indexes strings by UTF-16 code units; the checked-dynamic tree stores UTF-8.
|
||||
* Code points walk one at a time — an astral code point stays WHOLE
|
||||
* (one entry where JS lists two lone surrogates; documented
|
||||
* approximation, the keys stay dense). */
|
||||
const ScrStr *s = v->v.str;
|
||||
size_t unit = 0;
|
||||
for (size_t i = 0; i < s->len;) {
|
||||
unsigned char c = (unsigned char)s->data[i];
|
||||
size_t step = c < 0x80 ? 1 : c < 0xe0 ? 2 : c < 0xf0 ? 3 : 4;
|
||||
size_t length = (size_t)scr_str_utf16_len(v->v.str);
|
||||
for (size_t unit = 0; unit < length; unit++) {
|
||||
char key[24];
|
||||
int klen = snprintf(key, sizeof key, "%zu", unit);
|
||||
if (mode == SCR_OBJWALK_KEYS) {
|
||||
scr_dyn_arr_push(out, scr_dyn_objwalk_key(key, (size_t)klen));
|
||||
} else {
|
||||
ScrDyn *val = scr_dyn_objwalk_key(s->data + i, step);
|
||||
ScrStr *character = scr_str_char_at(v->v.str, (double)unit);
|
||||
ScrDyn *val = scr_dyn_new_str(character);
|
||||
scr_str_release(character);
|
||||
if (mode == SCR_OBJWALK_VALUES) {
|
||||
scr_dyn_arr_push(out, val);
|
||||
} else {
|
||||
@@ -4775,8 +5147,6 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
|
||||
scr_dyn_arr_push(out, pair);
|
||||
}
|
||||
}
|
||||
unit++;
|
||||
i += step;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -4787,13 +5157,29 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
|
||||
|
||||
ScrDyn *scr_dyn_obj_keys(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_KEYS); }
|
||||
|
||||
/* Native dictionaries have only Object.prototype or a null prototype;
|
||||
* neither contributes enumerable keys. Snapshot names without invoking
|
||||
* getters; the loop rechecks live ownership before visiting each name. */
|
||||
/* Snapshot enumerable names from the receiver and each live prototype.
|
||||
* Non-enumerable own names shadow names farther up the chain. */
|
||||
ScrDyn *scr_dyn_for_in_keys(const ScrDyn *v) {
|
||||
if (scr_dyn_class_reflection_fence(v)) return NULL;
|
||||
switch (v->kind) {
|
||||
case SCR_DYN_OBJ:
|
||||
case SCR_DYN_OBJ: {
|
||||
ScrDyn *keys = scr_dyn_new_arr();
|
||||
ScrDyn *seen = scr_dyn_new_obj_null_proto();
|
||||
for (const ScrDyn *current = v; current; current = current->prototype) {
|
||||
ScrDyn *own = scr_dyn_obj_own_keys(current);
|
||||
for (size_t i = 0; i < own->v.arr.len; i++) {
|
||||
ScrDyn *key = own->v.arr.items[i];
|
||||
ScrStr *name = key->v.str;
|
||||
if (scr_dyn_has_own(seen, name)) continue;
|
||||
scr_dyn_obj_set(seen, name->data, name->len, scr_dyn_retain(scr_dyn_undefined()));
|
||||
if (scr_dyn_obj_enumerable(current, name->data, name->len))
|
||||
scr_dyn_arr_push(keys, scr_dyn_retain(key));
|
||||
}
|
||||
scr_dyn_release(own);
|
||||
}
|
||||
scr_dyn_release(seen);
|
||||
return keys;
|
||||
}
|
||||
case SCR_DYN_ARR:
|
||||
return scr_dyn_obj_keys(v);
|
||||
case SCR_DYN_STR: {
|
||||
@@ -5067,6 +5453,7 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) {
|
||||
}
|
||||
if (v->kind == SCR_DYN_ARR) {
|
||||
if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return true;
|
||||
if (v->v.arr.properties && scr_dyn_obj_get(v->v.arr.properties, key->data, key->len)) return true;
|
||||
return scr_dyn_canonical_own_index(key, v->v.arr.len);
|
||||
}
|
||||
if (v->kind == SCR_DYN_STR) {
|
||||
@@ -5090,6 +5477,25 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool scr_dyn_property_is_enumerable(const ScrDyn *value, const ScrStr *key) {
|
||||
if (scr_dyn_class_reflection_fence(value)) return false;
|
||||
if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) {
|
||||
static const char message[] = "Cannot convert undefined or null to object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return false;
|
||||
}
|
||||
if (value->kind == SCR_DYN_OBJ)
|
||||
return scr_dyn_obj_enumerable(value, key->data, key->len);
|
||||
if (value->kind == SCR_DYN_STR)
|
||||
return scr_dyn_canonical_own_index(key, (size_t)scr_str_utf16_len(value->v.str));
|
||||
if (value->kind == SCR_DYN_ARR)
|
||||
return scr_dyn_canonical_own_index(key, value->v.arr.len) ||
|
||||
(value->v.arr.properties && scr_dyn_obj_enumerable(value->v.arr.properties, key->data, key->len));
|
||||
if (value->kind == SCR_DYN_BYTES)
|
||||
return scr_dyn_canonical_own_index(key, value->v.bytes->len);
|
||||
return false;
|
||||
}
|
||||
ScrDyn *scr_dyn_obj_values(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_VALUES); }
|
||||
ScrDyn *scr_dyn_obj_entries(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_ENTRIES); }
|
||||
|
||||
@@ -6261,7 +6667,7 @@ ScrMap *scr_dyn_native_set_check(const ScrDyn *value, const ScrDynPath *path) {
|
||||
}
|
||||
|
||||
ScrStr *scr_dyn_to_string_method(const ScrDyn *d, const ScrStr *enc, const ScrStr *what) {
|
||||
if (d->kind == SCR_DYN_OBJ && d->null_proto) {
|
||||
if (d->kind == SCR_DYN_OBJ && !scr_dyn_property_owner(d, "toString", 8) && !scr_dyn_has_object_prototype(d)) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
for (size_t i = 0; i < what->len; i++) scr_jb_putc(&b, what->data[i]);
|
||||
@@ -6273,6 +6679,14 @@ ScrStr *scr_dyn_to_string_method(const ScrDyn *d, const ScrStr *enc, const ScrSt
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_freeze(ScrDyn *value) {
|
||||
if (scr_dyn_class_reflection_fence(value)) return NULL;
|
||||
if (value->kind == SCR_DYN_ARR) {
|
||||
value->non_extensible = true;
|
||||
value->v.arr.sealed = true;
|
||||
value->v.arr.frozen = true;
|
||||
if (value->v.arr.properties) scr_dyn_release(scr_dyn_freeze(value->v.arr.properties));
|
||||
return scr_dyn_retain(value);
|
||||
}
|
||||
if (value->kind == SCR_DYN_OBJ && !value->copied_from_native) {
|
||||
value->non_extensible = true;
|
||||
for (size_t i = 0; i < value->v.obj.len; i++) {
|
||||
@@ -6291,6 +6705,10 @@ ScrDyn *scr_dyn_freeze(ScrDyn *value) {
|
||||
}
|
||||
|
||||
bool scr_dyn_is_frozen(const ScrDyn *value) {
|
||||
if (scr_dyn_class_reflection_fence(value)) return false;
|
||||
if (value->kind == SCR_DYN_ARR)
|
||||
return scr_dyn_is_sealed(value) && (value->v.arr.len == 0 || value->v.arr.frozen) &&
|
||||
(!value->v.arr.properties || scr_dyn_is_frozen(value->v.arr.properties));
|
||||
if (value->kind == SCR_DYN_OBJ && !value->copied_from_native) {
|
||||
if (!value->non_extensible) return false;
|
||||
for (size_t i = 0; i < value->v.obj.len; i++) {
|
||||
|
||||
@@ -3454,14 +3454,14 @@ struct ScrDyn {
|
||||
/* A deep typed-to-dyn copy has no stable native reference identity.
|
||||
* Weak collections must refuse it instead of weakly tracking its box. */
|
||||
bool copied_from_native;
|
||||
bool non_extensible; /* ordinary checked objects; fits the header padding */
|
||||
bool non_extensible; /* native objects and arrays; fits the header padding */
|
||||
union {
|
||||
bool b;
|
||||
double num;
|
||||
ScrStr *str; /* owned */
|
||||
ScrBytes *bytes; /* owned (SCR_DYN_BYTES) */
|
||||
ScrBigInt *bigint; /* owned (SCR_DYN_BIGINT) */
|
||||
struct { size_t len; size_t cap; ScrDyn **items; } arr; /* owned */
|
||||
struct { size_t len; size_t cap; ScrDyn **items; struct ScrDyn *properties; bool sealed; bool frozen; } arr; /* owned */
|
||||
struct {
|
||||
size_t len;
|
||||
size_t cap;
|
||||
@@ -3517,6 +3517,9 @@ struct ScrDyn {
|
||||
struct { ScrJsval *cell; } jsval;
|
||||
struct { ScrDyn *target; ScrDyn *handler; } proxy; /* both owned */
|
||||
} v;
|
||||
/* Keep the checked value payload at its established offset: generated
|
||||
* native converters read array and object fields directly. */
|
||||
struct ScrDyn *prototype;
|
||||
};
|
||||
|
||||
static inline ScrDyn *scr_dyn_retain(ScrDyn *d) {
|
||||
@@ -3559,10 +3562,12 @@ ScrDyn *scr_dyn_obj_keys(const ScrDyn *v);
|
||||
ScrDyn *scr_dyn_for_in_keys(const ScrDyn *v);
|
||||
/* Snapshot all own string keys of a SCR_DYN_OBJ in JS order. Returns +1. */
|
||||
ScrDyn *scr_dyn_obj_own_keys(const ScrDyn *v);
|
||||
ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value);
|
||||
/* Object.hasOwn over a dyn receiver: OBJ member presence, ARR index
|
||||
* bounds ("length" included); nullish receivers throw Node's ToObject
|
||||
* TypeError; every other kind answers false. */
|
||||
bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key);
|
||||
bool scr_dyn_property_is_enumerable(const ScrDyn *value, const ScrStr *key);
|
||||
/* Object.assign over dyn values (+1 target back; ToObject TypeError on a
|
||||
* nullish target). Sources visit own keys in order and copy entries that
|
||||
* are enumerable when reached; ARR/STR/BYTES expose index keys. Nullish
|
||||
@@ -3623,6 +3628,15 @@ void *scr_dyn_obj_source_cast(const ScrDyn *d,
|
||||
/* Object.create(null): the fresh null-prototype dictionary (see the
|
||||
* null_proto flavor flag above). */
|
||||
ScrDyn *scr_dyn_new_obj_null_proto(void);
|
||||
ScrDyn *scr_dyn_obj_create(ScrDyn *prototype);
|
||||
ScrDyn *scr_dyn_obj_create_with_properties(ScrDyn *prototype, ScrDyn *descriptors);
|
||||
ScrDyn *scr_dyn_get_prototype(ScrDyn *object);
|
||||
ScrDyn *scr_dyn_get_own_property_descriptors(ScrDyn *object);
|
||||
ScrDyn *scr_dyn_set_prototype(ScrDyn *object, ScrDyn *prototype);
|
||||
ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object);
|
||||
bool scr_dyn_is_extensible(ScrDyn *object);
|
||||
ScrDyn *scr_dyn_seal(ScrDyn *object);
|
||||
bool scr_dyn_is_sealed(const ScrDyn *object);
|
||||
ScrDyn *scr_dyn_proxy_new(const ScrDyn *target, const ScrDyn *handler);
|
||||
ScrDyn *scr_dyn_proxy_get(const ScrDyn *proxy, const ScrStr *key);
|
||||
bool scr_dyn_proxy_has(const ScrDyn *proxy, const ScrStr *key);
|
||||
@@ -3698,6 +3712,7 @@ void scr_weak_dispose(void *object);
|
||||
* +1. */
|
||||
double scr_dyn_arr_len(const ScrDyn *d);
|
||||
ScrDyn *scr_dyn_arr_at(const ScrDyn *d, double i);
|
||||
ScrDyn *scr_dyn_arr_named_get(const ScrDyn *d, const ScrStr *key);
|
||||
void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value);
|
||||
ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor);
|
||||
ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
let reads = 0;
|
||||
class Provider {
|
||||
get callable() {
|
||||
reads++;
|
||||
return function(value) { return value + 1; };
|
||||
}
|
||||
}
|
||||
const provider = new Provider();
|
||||
console.log(provider.callable(4), reads);
|
||||
class PrivateProvider {
|
||||
#method(value = 2) { return value + 3; }
|
||||
get method() { return this.#method; }
|
||||
}
|
||||
console.log(new PrivateProvider().method());
|
||||
@@ -0,0 +1,19 @@
|
||||
class Base {
|
||||
value() { return 'base'; }
|
||||
}
|
||||
class Derived extends Base {
|
||||
value() { return 'derived'; }
|
||||
}
|
||||
const base = new Base();
|
||||
const derived = new Derived();
|
||||
console.log(base['value'](), derived['value']());
|
||||
let asBase = derived;
|
||||
console.log(asBase['value']());
|
||||
class Keyword {
|
||||
w\u0069th() { return 42; }
|
||||
}
|
||||
console.log(new Keyword()['with']());
|
||||
class Numeric {
|
||||
[7]() { return 'seven'; }
|
||||
}
|
||||
console.log(new Numeric()[7]());
|
||||
@@ -0,0 +1,13 @@
|
||||
const record = { 0: 2, 1: 4, 2: 6, length: 3 };
|
||||
console.log(Array.prototype.findLast.call(record, value => value < 6));
|
||||
console.log(Array.prototype.findLastIndex.call(record, value => value < 6));
|
||||
console.log(Array.prototype.findLast.call(record, value => value > 9));
|
||||
console.log(Array.prototype.findLastIndex.call(record, value => value > 9));
|
||||
|
||||
const sparse = { 0: 1, 2: 3, length: 3 };
|
||||
const seen = [];
|
||||
console.log(Array.prototype.findLastIndex.call(sparse, (value, index) => {
|
||||
seen.push(`${index}:${value}`);
|
||||
return value === undefined;
|
||||
}));
|
||||
console.log(seen.join(','));
|
||||
@@ -0,0 +1,13 @@
|
||||
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 };
|
||||
console.log(Array.prototype.slice.call(arrayLike).join(','));
|
||||
console.log(Array.prototype.slice.call(arrayLike, 1, 3).join(','));
|
||||
console.log(Array.prototype.slice.call(arrayLike, -2).join(','));
|
||||
console.log(Array.prototype.slice.call(arrayLike, 0, -1).join(','));
|
||||
console.log(Array.prototype.slice.call(arrayLike, 8).length);
|
||||
console.log(Array.prototype.slice.call(arrayLike, -9, 2).join(','));
|
||||
console.log(Array.prototype.slice.call(arrayLike, 3, 1).length);
|
||||
console.log(Array.prototype.slice.call(arrayLike, 1, undefined).join(','));
|
||||
|
||||
const values = JSON.parse('[1,2,3,4]');
|
||||
console.log(Array.prototype.slice.call(values, -3, -1).join(','));
|
||||
console.log(values.join(','));
|
||||
@@ -0,0 +1,15 @@
|
||||
const values = JSON.parse('[4,1,3,2]');
|
||||
console.log(values.toReversed().join(','), values.join(','));
|
||||
console.log(values.toSorted().join(','), values.join(','));
|
||||
console.log(values.toSorted((a, b) => b - a).join(','));
|
||||
console.log(values.toSpliced(1, 2, 8, 9).join(','), values.join(','));
|
||||
console.log(values.toSpliced(-2, 1).join(','));
|
||||
console.log(values.with(-1, 7).join(','), values.join(','));
|
||||
try { values.with(9, 0); } catch (error) { console.log(error.name); }
|
||||
|
||||
Object.freeze(values);
|
||||
console.log(values.toReversed().join(','));
|
||||
console.log(values.toSorted().join(','));
|
||||
console.log(values.toSpliced(0, 1, 6).join(','));
|
||||
console.log(values.with(0, 5).join(','));
|
||||
console.log(values.join(','));
|
||||
@@ -0,0 +1,17 @@
|
||||
const values = JSON.parse('[1,2,3,4,5]');
|
||||
console.log(values.fill(8, 1, -1) === values, values.join(','));
|
||||
console.log(values.copyWithin(2, 0, 3) === values, values.join(','));
|
||||
console.log(values.copyWithin(0, 2, 5).join(','));
|
||||
console.log(values.copyWithin(-2, 0, 2).join(','));
|
||||
|
||||
const frozen = JSON.parse('[1,2,3]');
|
||||
Object.freeze(frozen);
|
||||
try { frozen.fill(9); } catch (error) { console.log(error.name); }
|
||||
try { frozen.copyWithin(1, 0); } catch (error) { console.log(error.name); }
|
||||
console.log(frozen.join(','));
|
||||
|
||||
const sealed = JSON.parse('[1,2,3]');
|
||||
Object.seal(sealed);
|
||||
sealed.fill(7, 1);
|
||||
sealed.copyWithin(0, 1);
|
||||
console.log(sealed.join(','));
|
||||
@@ -0,0 +1,21 @@
|
||||
const empty = JSON.parse('[]');
|
||||
Object.freeze(empty);
|
||||
console.log(empty.sort().length, empty.reverse().length);
|
||||
console.log(empty.fill(4).length, empty.copyWithin(0, 0).length);
|
||||
for (const operation of [() => empty.push(), () => empty.pop(), () => empty.shift(), () => empty.unshift(), () => empty.splice(0, 0)]) {
|
||||
try { operation(); } catch (error) { console.log(error.name); }
|
||||
}
|
||||
|
||||
const one = JSON.parse('[1]');
|
||||
Object.freeze(one);
|
||||
console.log(one.sort().join(','), one.reverse().join(','));
|
||||
console.log(one.fill(9, 0, 0).join(','), one.copyWithin(0, 1).join(','));
|
||||
try { one.fill(9); } catch (error) { console.log(error.name); }
|
||||
try { one.copyWithin(0, 0); } catch (error) { console.log(error.name); }
|
||||
console.log(one.join(','));
|
||||
|
||||
const two = JSON.parse('[2,1]');
|
||||
Object.freeze(two);
|
||||
try { two.sort(); } catch (error) { console.log(error.name); }
|
||||
try { two.reverse(); } catch (error) { console.log(error.name); }
|
||||
console.log(two.join(','));
|
||||
@@ -0,0 +1,28 @@
|
||||
const values = JSON.parse('[4,2,4,1]');
|
||||
console.log(values.indexOf(4), values.indexOf(4, 1), values.indexOf(4, -2));
|
||||
console.log(values.lastIndexOf(4), values.lastIndexOf(4, 1), values.lastIndexOf(4, -3));
|
||||
console.log(values.includes(4, 3), values.includes(4, -2), values.includes(4, Infinity));
|
||||
console.log(values.findLast((value) => value > 1), values.findLastIndex((value) => value > 1));
|
||||
console.log(values.findLast((value) => value > 9), values.findLastIndex((value) => value > 9));
|
||||
console.log(values.slice(-3, -1).join(','), values.slice(99).join(','));
|
||||
console.log(values.toReversed().join(','), values.join(','));
|
||||
console.log(values.toSpliced(0, 0, 8).join(','), values.join(','));
|
||||
console.log(values.with(-4, 7).join(','), values.join(','));
|
||||
|
||||
const shifted = JSON.parse('[1,2,3]');
|
||||
console.log(shifted.unshift(8, 9), shifted.join(','));
|
||||
console.log(shifted.unshift(), shifted.join(','));
|
||||
console.log(shifted.splice(1, 2, 4, 5, 6).join(','), shifted.join(','));
|
||||
console.log(shifted.splice(-1, 1, 7).join(','), shifted.join(','));
|
||||
|
||||
const copied = JSON.parse('[0,1,2,3,4]');
|
||||
console.log(copied.copyWithin(1, 3).join(','));
|
||||
console.log(copied.copyWithin(-2, 0, 2).join(','));
|
||||
console.log(copied.fill(6, -3, -1).join(','));
|
||||
console.log(copied.fill(7, 0, 0).join(','));
|
||||
|
||||
const nested = JSON.parse('[1,[2,[3]],4]');
|
||||
console.log(nested.flat(0).join(','));
|
||||
console.log(nested.flat(1).join(','));
|
||||
console.log(nested.flat(2).join(','));
|
||||
console.log(JSON.stringify(nested));
|
||||
@@ -0,0 +1,27 @@
|
||||
const sealed = JSON.parse('[1,2]');
|
||||
Object.seal(sealed);
|
||||
console.log(Object.isExtensible(sealed), Object.isSealed(sealed), Object.isFrozen(sealed));
|
||||
console.log(Object.getOwnPropertyDescriptor(sealed, '0').configurable);
|
||||
console.log(Object.getOwnPropertyDescriptor(sealed, '0').writable);
|
||||
sealed[0] = 4;
|
||||
console.log(sealed[0]);
|
||||
for (const operation of [() => sealed.pop(), () => sealed.shift(), () => sealed.push(3)]) {
|
||||
try { operation(); } catch (error) { console.log(error.name); }
|
||||
}
|
||||
console.log(sealed.join(','));
|
||||
|
||||
const frozen = JSON.parse('[3,5]');
|
||||
Object.freeze(frozen);
|
||||
console.log(Object.isSealed(frozen), Object.isFrozen(frozen));
|
||||
console.log(Object.getOwnPropertyDescriptor(frozen, '0').writable);
|
||||
console.log(Object.getOwnPropertyDescriptor(frozen, 'length').writable);
|
||||
for (const operation of [() => { frozen[0] = 9; }, () => frozen.reverse(), () => frozen.sort()]) {
|
||||
try { operation(); } catch (error) { console.log(error.name); }
|
||||
}
|
||||
console.log(frozen.join(','));
|
||||
|
||||
const empty = JSON.parse('[]');
|
||||
Object.preventExtensions(empty);
|
||||
console.log(Object.isSealed(empty), Object.isFrozen(empty));
|
||||
try { empty.push(1); } catch (error) { console.log(error.name); }
|
||||
console.log(empty.length);
|
||||
@@ -0,0 +1,19 @@
|
||||
const values = JSON.parse('[1,2,3,4]');
|
||||
values.length = 2;
|
||||
console.log(values.length, values.join(','), Object.keys(values).join(','));
|
||||
values.length = 2;
|
||||
console.log(values.length, Object.hasOwn(values, '2'));
|
||||
values[2] = 8;
|
||||
console.log(values.join(','));
|
||||
try { values.length = -1; } catch (error) { console.log(error.name); }
|
||||
console.log(values.length);
|
||||
|
||||
const sealed = JSON.parse('[1,2,3]');
|
||||
Object.seal(sealed);
|
||||
try { sealed.length = 1; } catch (error) { console.log(error.name); }
|
||||
console.log(sealed.length, sealed.join(','));
|
||||
|
||||
const frozen = JSON.parse('[1,2]');
|
||||
Object.freeze(frozen);
|
||||
try { frozen.length = 1; } catch (error) { console.log(error.name); }
|
||||
console.log(frozen.length, frozen.join(','));
|
||||
@@ -0,0 +1,21 @@
|
||||
const values = JSON.parse('[3,5]');
|
||||
values.extra = 7;
|
||||
Object.defineProperty(values, 'hidden', { value: 9, writable: true, configurable: true });
|
||||
console.log(values.extra, values.hidden, values.length);
|
||||
console.log(Object.keys(values).join(','));
|
||||
console.log(Object.getOwnPropertyNames(values).join(','));
|
||||
console.log(Object.hasOwn(values, 'extra'), 'hidden' in values);
|
||||
console.log(Object.getOwnPropertyDescriptor(values, 'hidden').enumerable);
|
||||
console.log(Object.getOwnPropertyDescriptors(values).extra.value);
|
||||
const keys = [];
|
||||
for (const key in values) keys.push(key);
|
||||
console.log(keys.join(','));
|
||||
delete values.hidden;
|
||||
console.log(Object.hasOwn(values, 'hidden'));
|
||||
Object.seal(values);
|
||||
console.log(Object.isSealed(values), Object.isFrozen(values));
|
||||
console.log(Object.getOwnPropertyDescriptor(values, 'extra').configurable);
|
||||
values.extra = 8;
|
||||
console.log(values.extra);
|
||||
Object.freeze(values);
|
||||
console.log(Object.isFrozen(values), Object.getOwnPropertyDescriptor(values, 'extra').writable);
|
||||
@@ -0,0 +1,18 @@
|
||||
const values = JSON.parse('[1,2,3,4]');
|
||||
console.log(values.reduce((a, b) => a + b));
|
||||
console.log(values.reduceRight((a, b) => a - b));
|
||||
console.log(values.reduce((a, b) => a + b, 10));
|
||||
console.log(values.findLast(x => x < 4), values.findLastIndex(x => x < 4));
|
||||
console.log(values.findLast(x => x > 9), values.findLastIndex(x => x > 9));
|
||||
try { JSON.parse('[]').reduce((a, b) => a + b); } catch (error) { console.log(error.name); }
|
||||
console.log(JSON.parse('[]').reduce((a, b) => a + b, 5));
|
||||
console.log(JSON.parse('[7]').reduce((a, b) => a + b));
|
||||
console.log(JSON.parse('[7]').reduceRight((a, b) => a + b));
|
||||
const shrinking = JSON.parse('[1,2,3]');
|
||||
const seen = [];
|
||||
const found = shrinking.findLast((value, index) => {
|
||||
seen.push(`${index}:${value}`);
|
||||
if (index === 2) shrinking.length = 1;
|
||||
return false;
|
||||
});
|
||||
console.log(found, seen.join(','), shrinking.join(','));
|
||||
@@ -0,0 +1,11 @@
|
||||
const values = JSON.parse('[1,2,1,3]');
|
||||
console.log(values.indexOf(1), values.indexOf(1, 1), values.indexOf(1, -2));
|
||||
console.log(values.indexOf(2, 3), values.indexOf(2, -9));
|
||||
console.log(values.lastIndexOf(1), values.lastIndexOf(1, 1), values.lastIndexOf(1, -2));
|
||||
console.log(values.lastIndexOf(3, -9), values.lastIndexOf(3, 99));
|
||||
console.log(values.includes(1, 2), values.includes(1, 3), values.includes(3, -1));
|
||||
console.log(values.includes(2, 8), values.includes(2, -8));
|
||||
const empty = JSON.parse('[]');
|
||||
let conversions = 0;
|
||||
const from = { valueOf() { conversions++; return 0; } };
|
||||
console.log(empty.indexOf(1, from), empty.lastIndexOf(1, from), empty.includes(1, from), conversions);
|
||||
@@ -0,0 +1,18 @@
|
||||
const values = JSON.parse('[0,1,2,3]');
|
||||
let conversions = 0;
|
||||
const start = { valueOf() { conversions++; return 1; } };
|
||||
const count = { valueOf() { conversions++; return 2; } };
|
||||
console.log(values.splice(start, count, 8, 9).join(','));
|
||||
console.log(values.join(','), conversions);
|
||||
console.log(values.splice(-1, 0, 7).join(','), values.join(','));
|
||||
|
||||
const sealed = JSON.parse('[1,2]');
|
||||
Object.seal(sealed);
|
||||
let sealedConversions = 0;
|
||||
const sealedStart = { valueOf() { sealedConversions++; return 0; } };
|
||||
console.log(sealed.splice(sealedStart, 1, 3).join(','), sealed.join(','), sealedConversions);
|
||||
|
||||
const frozen = JSON.parse('[4,5]');
|
||||
Object.freeze(frozen);
|
||||
try { frozen.splice(0, 1); } catch (error) { console.log(error.name); }
|
||||
console.log(frozen.join(','));
|
||||
@@ -0,0 +1,18 @@
|
||||
const values = JSON.parse('[0,1,2,3,4]');
|
||||
console.log(values.splice(1, 2, 8, 9).join(','), values.join(','));
|
||||
console.log(values.splice(-2, 1).join(','), values.join(','));
|
||||
console.log(values.splice(1, 0, 7).join(','), values.join(','));
|
||||
console.log(values.splice(2).join(','), values.join(','));
|
||||
console.log(values.splice(9, 2).join(','), values.join(','));
|
||||
|
||||
const nested = JSON.parse('[1,[2,[3,[4]]],5]');
|
||||
console.log(JSON.stringify(nested.flat()));
|
||||
console.log(JSON.stringify(nested.flat(2)));
|
||||
console.log(JSON.stringify(nested.flat(0)));
|
||||
console.log(JSON.stringify(nested));
|
||||
|
||||
const sealed = JSON.parse('[1,2,3]');
|
||||
Object.seal(sealed);
|
||||
console.log(sealed.splice(1, 1, 7).join(','), sealed.join(','));
|
||||
try { sealed.splice(1, 1); } catch (error) { console.log(error.name); }
|
||||
console.log(sealed.join(','));
|
||||
@@ -0,0 +1,12 @@
|
||||
const object = JSON.parse('{"first":1,"second":2}');
|
||||
Object.defineProperty(object, 'hidden', { value: 3 });
|
||||
const descriptors = Object.getOwnPropertyDescriptors(object);
|
||||
console.log(Object.keys(descriptors).join(','));
|
||||
console.log(descriptors.first.value, descriptors.first.writable, descriptors.first.enumerable, descriptors.first.configurable);
|
||||
console.log(descriptors.hidden.value, descriptors.hidden.writable, descriptors.hidden.enumerable, descriptors.hidden.configurable);
|
||||
console.log(Object.getOwnPropertyDescriptors(3));
|
||||
console.log(Object.keys(Object.getOwnPropertyDescriptors('cat')).join(','));
|
||||
const parent = JSON.parse('{"inherited":4}');
|
||||
const child = Object.create(parent);
|
||||
child.own = 5;
|
||||
console.log(Object.keys(Object.getOwnPropertyDescriptors(child)).join(','));
|
||||
@@ -0,0 +1,11 @@
|
||||
const parent = JSON.parse('{"first":1,"second":2}');
|
||||
const child = Object.create(parent);
|
||||
console.log(Object.keys(parent).join(','));
|
||||
console.log(Object.getOwnPropertyNames(parent).join(','));
|
||||
console.log('first' in child);
|
||||
const parentKeys = [];
|
||||
for (const key in parent) parentKeys.push(key);
|
||||
console.log(parentKeys.join(','));
|
||||
const keys = [];
|
||||
for (const key in child) keys.push(key);
|
||||
console.log(keys.join(','));
|
||||
@@ -0,0 +1,50 @@
|
||||
const target = JSON.parse('{"visible":1}');
|
||||
Object.defineProperty(target, 'hidden', { value: 2, writable: true, configurable: true });
|
||||
console.log(Object.keys(target).join(','));
|
||||
const absent = JSON.parse('null');
|
||||
try { Object.prototype.propertyIsEnumerable.call(absent, 'x'); }
|
||||
catch (error) { console.log(error.name); }
|
||||
console.log(Object.getOwnPropertyNames(target).join(','));
|
||||
console.log(target.hidden, Object.hasOwn(target, 'hidden'), target.propertyIsEnumerable('hidden'));
|
||||
const first = Object.getOwnPropertyDescriptor(target, 'hidden');
|
||||
console.log(first.value, first.writable, first.enumerable, first.configurable);
|
||||
Object.defineProperty(target, 'hidden', { value: 3, enumerable: true });
|
||||
const second = Object.getOwnPropertyDescriptor(target, 'hidden');
|
||||
console.log(second.value, second.writable, second.enumerable, second.configurable);
|
||||
console.log(Object.keys(target).join(','), Object.values(target).join(','));
|
||||
|
||||
const parent = JSON.parse('{"inherited":4}');
|
||||
const child = Object.create(parent);
|
||||
const omittedDescriptors = Object.create(parent, undefined);
|
||||
console.log(omittedDescriptors.inherited, Object.getOwnPropertyNames(omittedDescriptors).length);
|
||||
Object.defineProperty(child, 'own', { value: 5, enumerable: true, writable: true, configurable: true });
|
||||
console.log(Object.keys(child).join(','), Object.getOwnPropertyNames(child).join(','));
|
||||
console.log(Object.hasOwn(child, 'inherited'), 'inherited' in child);
|
||||
const inherited = Object.getOwnPropertyDescriptor(child, 'inherited');
|
||||
console.log(inherited === undefined, child.inherited, Object.getPrototypeOf(child) === parent);
|
||||
const visited = [];
|
||||
for (const key in child) visited.push(key);
|
||||
console.log(visited.join(','));
|
||||
Object.defineProperty(child, 'inherited', { value: 6, enumerable: false, writable: true });
|
||||
const shadowed = [];
|
||||
for (const key in child) shadowed.push(key);
|
||||
console.log(child.inherited, parent.inherited, shadowed.join(','));
|
||||
|
||||
const fixed = JSON.parse('{"item":7}');
|
||||
Object.freeze(fixed);
|
||||
const frozen = Object.getOwnPropertyDescriptors(fixed);
|
||||
console.log(frozen.item.value, frozen.item.writable, frozen.item.configurable);
|
||||
console.log(Object.isExtensible(fixed), Object.isSealed(fixed), Object.isFrozen(fixed));
|
||||
try { Object.defineProperty(fixed, 'item', { value: 8 }); }
|
||||
catch (error) { console.log(error.name); }
|
||||
try { Object.defineProperty(fixed, 'new', { value: 9 }); }
|
||||
catch (error) { console.log(error.name); }
|
||||
console.log(fixed.item, Object.hasOwn(fixed, 'new'));
|
||||
|
||||
const nullProto = Object.create(null);
|
||||
nullProto.key = 10;
|
||||
console.log(Object.getPrototypeOf(nullProto) === null, Object.keys(nullProto).join(','));
|
||||
Object.preventExtensions(nullProto);
|
||||
console.log(Object.isExtensible(nullProto), Object.isSealed(nullProto), Object.isFrozen(nullProto));
|
||||
Object.seal(nullProto);
|
||||
console.log(Object.isSealed(nullProto), Object.isFrozen(nullProto));
|
||||
@@ -0,0 +1,44 @@
|
||||
const parent = JSON.parse('{"value": 1}');
|
||||
const child = Object.create(parent);
|
||||
console.log(child.value, Object.keys(child).length, Object.hasOwn(child, 'value'), 'value' in child);
|
||||
console.log(child.propertyIsEnumerable('value'), parent.propertyIsEnumerable('value'));
|
||||
parent.value = 2;
|
||||
console.log(child.value);
|
||||
child.value = 3;
|
||||
console.log(child.value, parent.value, Object.hasOwn(child, 'value'));
|
||||
const grandchild = Object.create(child);
|
||||
console.log(grandchild.value);
|
||||
Object.defineProperty(parent, 'inherited', { get: function() { return this.value + 1; }, enumerable: true });
|
||||
console.log(grandchild.inherited, Object.keys(grandchild).length);
|
||||
console.log(grandchild.propertyIsEnumerable('inherited'), parent.propertyIsEnumerable('inherited'));
|
||||
console.log(Object.getOwnPropertyNames(grandchild).length, Object.getOwnPropertyDescriptor(grandchild, 'value'));
|
||||
console.log(Object.getPrototypeOf(child) === parent, Object.getPrototypeOf(grandchild) === child);
|
||||
const inheritedKeys = [];
|
||||
for (const key in grandchild) inheritedKeys.push(key);
|
||||
console.log(inheritedKeys.join(','));
|
||||
Object.defineProperty(child, 'value', { value: 5, enumerable: false, writable: true, configurable: true });
|
||||
const shadowedKeys = [];
|
||||
for (const key in grandchild) shadowedKeys.push(key);
|
||||
console.log(grandchild.value, shadowedKeys.join(','));
|
||||
const replacement = JSON.parse('{"replacement": 8}');
|
||||
Object.setPrototypeOf(grandchild, replacement);
|
||||
console.log(Object.getPrototypeOf(grandchild) === replacement, grandchild.replacement, 'value' in grandchild);
|
||||
const descriptors = JSON.parse('{"own":{"value":11,"enumerable":true,"writable":true}}');
|
||||
const described = Object.create(replacement, descriptors);
|
||||
console.log(described.own, described.replacement, Object.keys(described).join(','));
|
||||
const locked = JSON.parse('{"first":1,"second":2}');
|
||||
console.log(Object.isExtensible(locked), Object.isSealed(locked), Object.isFrozen(locked));
|
||||
Object.preventExtensions(locked);
|
||||
console.log(Object.isExtensible(locked), Object.isSealed(locked), Object.isFrozen(locked));
|
||||
try { locked.third = 3; } catch (error) { console.log(error.name); }
|
||||
console.log(Object.hasOwn(locked, 'third'));
|
||||
const sealed = JSON.parse('{"first":1}');
|
||||
Object.seal(sealed);
|
||||
console.log(Object.isSealed(sealed), Object.isFrozen(sealed), Object.getOwnPropertyDescriptor(sealed, 'first').configurable);
|
||||
sealed.first = 4;
|
||||
console.log(sealed.first);
|
||||
const frozen = JSON.parse('{"first":1}');
|
||||
Object.freeze(frozen);
|
||||
console.log(Object.isSealed(frozen), Object.isFrozen(frozen), Object.getOwnPropertyDescriptor(frozen, 'first').writable);
|
||||
try { frozen.first = 4; } catch (error) { console.log(error.name); }
|
||||
console.log(frozen.first);
|
||||
@@ -0,0 +1,14 @@
|
||||
console.log(Object.getOwnPropertyDescriptor(true, 'x'));
|
||||
console.log(Object.getOwnPropertyDescriptor(7, 'x'));
|
||||
const text = Object.getOwnPropertyDescriptor('abc', '1');
|
||||
console.log(text.value, text.writable, text.enumerable, text.configurable);
|
||||
const length = Object.getOwnPropertyDescriptor('abc', 'length');
|
||||
console.log(length.value, length.writable, length.enumerable, length.configurable);
|
||||
function item(value) { return value; }
|
||||
item[1] = 'first';
|
||||
item[1] = 'second';
|
||||
const own = Object.getOwnPropertyDescriptor(item, '1');
|
||||
console.log(own.value, own.writable, own.enumerable, own.configurable);
|
||||
Object.defineProperty(item, 'fixed', { value: 42, enumerable: false, writable: false, configurable: false });
|
||||
const fixed = Object.getOwnPropertyDescriptor(item, 'fixed');
|
||||
console.log(item['fixed'], fixed.value, fixed.writable, fixed.enumerable, fixed.configurable);
|
||||
@@ -1,17 +1,4 @@
|
||||
// Object.create's fence battery: only the null-prototype dictionary
|
||||
// lowers in a static build (Object.create(null) — corpus 2581/2582).
|
||||
// Every other prototype is a NAMED fence, not an own-copy stand-in: Node
|
||||
// lists NO own keys on the created object (Object.keys/inspect/JSON all
|
||||
// answer empty) and mutating the prototype afterwards shows through it
|
||||
// LIVE — observations a copy answers wrong silently.
|
||||
|
||||
// A static record prototype.
|
||||
// Statically shaped records cannot serve as live checked-dynamic prototypes.
|
||||
const base = { indent: 2 };
|
||||
const viaRecord = Object.create(base);
|
||||
|
||||
// A checked-dynamic (dyn) prototype.
|
||||
const dynProto: object = JSON.parse('{"a":1}');
|
||||
const viaDyn = Object.create(dynProto);
|
||||
|
||||
// The properties-descriptor form.
|
||||
const withDescriptors = Object.create(null, { a: { value: 1, enumerable: true } });
|
||||
base.indent = 4;
|
||||
|
||||
@@ -1,26 +1,8 @@
|
||||
object-create-fences.ts:10:19 - error SC2020: 'Object.create over '{ indent: number }' prototypes' is part of the standard library types but has no scriptc lowering yet
|
||||
object-create-fences.ts:3:19 - error SC2020: 'Object.create over '{ indent: number }' prototypes' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
9 | const base = { indent: 2 };
|
||||
10 | const viaRecord = Object.create(base);
|
||||
| ^~~~~~~~~~~~~~~~~~~
|
||||
11 |
|
||||
2 | const base = { indent: 2 };
|
||||
3 | const viaRecord = Object.create(base);
|
||||
| ^~~~~~~~~~~~~~~~~~~
|
||||
4 | base.indent = 4;
|
||||
|
||||
hint: the compiled representations have no prototype chain, and an own-copy would answer wrong observably (Node lists NO own keys on the created object, and prototype mutations show through it live) — only Object.create(null) lowers
|
||||
|
||||
object-create-fences.ts:14:16 - error SC2020: 'Object.create over 'unknown' prototypes' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
13 | const dynProto: object = JSON.parse('{"a":1}');
|
||||
14 | const viaDyn = Object.create(dynProto);
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~
|
||||
15 |
|
||||
|
||||
hint: the compiled representations have no prototype chain, and an own-copy would answer wrong observably (Node lists NO own keys on the created object, and prototype mutations show through it live) — only Object.create(null) lowers
|
||||
|
||||
object-create-fences.ts:17:25 - error SC2020: 'Object.create with a properties-descriptor argument' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
16 | // The properties-descriptor form.
|
||||
17 | const withDescriptors = Object.create(null, { a: { value: 1, enumerable: true } });
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
18 |
|
||||
|
||||
hint: create first, then assign: const o = Object.create(null); o.k = v
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
Reference in New Issue
Block a user