mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 08:35:07 +08:00
Expand static Test262 execution and exception behavior (#486)
* Expand static Test262 harness and exception semantics * Ignore ASan fiber warning in dev source test * Refresh TS7 parity baselines after main rebase
This commit is contained in:
@@ -3266,6 +3266,10 @@ function emitDynamicExpr(
|
||||
const test = `scr_caught_instanceof(${c.name}, ${target.pre}, ${target.post})`;
|
||||
return emitter.newTemp(e.type, e.negated ? `!${test}` : test);
|
||||
}
|
||||
if (e.test === "object") {
|
||||
const test = `scr_caught_is_object(${c.name})`;
|
||||
return emitter.newTemp(e.type, e.negated ? `!${test}` : test);
|
||||
}
|
||||
const tag = { string: "SCR_EXC_STR", number: "SCR_EXC_F64", boolean: "SCR_EXC_BOOL" }[e.test];
|
||||
return emitter.newTemp(e.type, `${c.name}->kind ${e.negated ? "!=" : "=="} ${tag}`);
|
||||
}
|
||||
@@ -3383,7 +3387,7 @@ function emitIntrinsicExpr(
|
||||
// bindings and the unhandled dispatch see the dyn value
|
||||
// itself — identity preserved.
|
||||
emitter.line(
|
||||
`scr_throw_ref(${reason.name}, &${rc.retain}, &${rc.release}, NULL);${emitter.srcComment(e.loc)}`,
|
||||
`scr_throw_ref_classified(${reason.name}, &${rc.retain}, &${rc.release}, NULL, scr_dyn_is_object(${reason.name}));${emitter.srcComment(e.loc)}`,
|
||||
);
|
||||
} else {
|
||||
emitter.line(
|
||||
@@ -3643,6 +3647,13 @@ function emitAsyncExpr(
|
||||
} else if (t.kind === "object" && emitter.classMeta.get(t.className)?.hierarchy) {
|
||||
const rc = vAdapters(t);
|
||||
emitter.line(`scr_throw_obj(${a.name}, &${rc.retain}, &${rc.release}, ${emitter.traceArgC(t)});${emitter.srcComment(e.loc)}`);
|
||||
} else if (t.kind === "symbol" || t.kind === "bigint" || t.kind === "func" || t.kind === "classval") {
|
||||
const rc = vAdapters(t);
|
||||
emitter.line(`scr_throw_primitive_ref(${a.name}, &${rc.retain}, &${rc.release}, NULL);${emitter.srcComment(e.loc)}`);
|
||||
} else if (t.kind === "dyn" || t.kind === "jsval") {
|
||||
const rc = vAdapters(t);
|
||||
const test = t.kind === "dyn" ? "scr_dyn_is_object" : "scr_jsval_is_object";
|
||||
emitter.line(`scr_throw_ref_classified(${a.name}, &${rc.retain}, &${rc.release}, ${emitter.traceArgC(t)}, ${test}(${a.name}));${emitter.srcComment(e.loc)}`);
|
||||
} else {
|
||||
const rc = vAdapters(t);
|
||||
emitter.line(`scr_throw_ref(${a.name}, &${rc.retain}, &${rc.release}, ${emitter.traceArgC(t)});${emitter.srcComment(e.loc)}`);
|
||||
@@ -3699,6 +3710,13 @@ function emitAsyncExpr(
|
||||
} else if (t.kind === "object" && emitter.classMeta.get(t.className)?.hierarchy) {
|
||||
const rc = vAdapters(t);
|
||||
emitter.line(`scr_throw_obj(${a.name}, &${rc.retain}, &${rc.release}, ${emitter.traceArgC(t)});${emitter.srcComment(e.loc)}`);
|
||||
} else if (t.kind === "symbol" || t.kind === "bigint" || t.kind === "func" || t.kind === "classval") {
|
||||
const rc = vAdapters(t);
|
||||
emitter.line(`scr_throw_primitive_ref(${a.name}, &${rc.retain}, &${rc.release}, NULL);${emitter.srcComment(e.loc)}`);
|
||||
} else if (t.kind === "dyn" || t.kind === "jsval") {
|
||||
const rc = vAdapters(t);
|
||||
const test = t.kind === "dyn" ? "scr_dyn_is_object" : "scr_jsval_is_object";
|
||||
emitter.line(`scr_throw_ref_classified(${a.name}, &${rc.retain}, &${rc.release}, ${emitter.traceArgC(t)}, ${test}(${a.name}));${emitter.srcComment(e.loc)}`);
|
||||
} else {
|
||||
const rc = vAdapters(t);
|
||||
emitter.line(`scr_throw_ref(${a.name}, &${rc.retain}, &${rc.release}, ${emitter.traceArgC(t)});${emitter.srcComment(e.loc)}`);
|
||||
|
||||
@@ -721,6 +721,13 @@ function emitStmtBody(emitter: CEmitter, s: IrStmt): void {
|
||||
// uncaught printer's "name: message" for Error instances).
|
||||
const rc = vAdapters(t);
|
||||
emitter.line(`scr_throw_obj(${v.name}, &${rc.retain}, &${rc.release}, ${emitter.traceArgC(t)});${emitter.srcComment(s.loc)}`);
|
||||
} else if (t.kind === "symbol" || t.kind === "bigint" || t.kind === "func" || t.kind === "classval") {
|
||||
const rc = vAdapters(t);
|
||||
emitter.line(`scr_throw_primitive_ref(${v.name}, &${rc.retain}, &${rc.release}, NULL);${emitter.srcComment(s.loc)}`);
|
||||
} else if (t.kind === "dyn" || t.kind === "jsval") {
|
||||
const rc = vAdapters(t);
|
||||
const test = t.kind === "dyn" ? "scr_dyn_is_object" : "scr_jsval_is_object";
|
||||
emitter.line(`scr_throw_ref_classified(${v.name}, &${rc.retain}, &${rc.release}, ${emitter.traceArgC(t)}, ${test}(${v.name}));${emitter.srcComment(s.loc)}`);
|
||||
} else {
|
||||
const rc = vAdapters(t);
|
||||
emitter.line(`scr_throw_ref(${v.name}, &${rc.retain}, &${rc.release}, ${emitter.traceArgC(t)});${emitter.srcComment(s.loc)}`);
|
||||
|
||||
@@ -378,6 +378,7 @@ import { OVERFLOW_MEMBER } from "./shapes.js";
|
||||
` }`,
|
||||
` /* FALLTHROUGH: a non-Error hierarchy object is type-erased */`,
|
||||
` case SCR_EXC_REF:`,
|
||||
` case SCR_EXC_PRIMITIVE_REF:`,
|
||||
` /* A thrown dyn value passes back BY REFERENCE (identity with`,
|
||||
` * every other holder of the node — the traced-throw shape). */`,
|
||||
` if (c->retain_fn == scr_dyn_retain_v) return scr_dyn_retain((ScrDyn *)c->payload);`,
|
||||
@@ -1013,8 +1014,7 @@ export function jsonWriteHelper(emitter: CEmitter, t: IrType): string {
|
||||
d.push(` return sc_out;`);
|
||||
d.push(` }`);
|
||||
d.push(` if (d->kind == SCR_DYN_OBJ) {`);
|
||||
d.push(` ScrDyn *m = scr_dyn_obj_get(d, k->data, k->len);`);
|
||||
d.push(` return scr_dyn_retain(m ? m : scr_dyn_undefined());`);
|
||||
d.push(` return scr_dyn_obj_read(d, k->data, k->len);`);
|
||||
d.push(` }`);
|
||||
d.push(` if (d->kind == SCR_DYN_JSVAL) {`);
|
||||
d.push(` /* Island-held: o[k] reads the REAL engine property (getters`);
|
||||
|
||||
@@ -2256,7 +2256,7 @@ export class LlDyn {
|
||||
B.terminate(`ret ptr ${r}`);
|
||||
B.startBlock(lNext);
|
||||
}
|
||||
// OBJ: the own member (+1) or the undefined singleton.
|
||||
// OBJ: the own member or the inherited builtin Error constructor.
|
||||
{
|
||||
const isObj = B.tmp();
|
||||
B.line(`${isObj} = icmp eq i32 ${kd}, ${DYN_KIND.OBJ}`);
|
||||
@@ -2264,15 +2264,9 @@ export class LlDyn {
|
||||
const lNext = B.newLabel("kg.n");
|
||||
B.condBr(isObj, lObj, lNext);
|
||||
B.startBlock(lObj);
|
||||
host.declare(`declare ptr @scr_dyn_obj_get(ptr, ptr, ${host.sizeType})`);
|
||||
const m = B.tmp();
|
||||
B.line(`${m} = call ptr @scr_dyn_obj_get(ptr %d, ptr ${kParts.data}, ${host.sizeType} ${kParts.len})`);
|
||||
const has = B.tmp();
|
||||
B.line(`${has} = icmp ne ptr ${m}, null`);
|
||||
const u = this.undef(B);
|
||||
const sel = B.tmp();
|
||||
B.line(`${sel} = select i1 ${has}, ptr ${m}, ptr ${u}`);
|
||||
const r = this.retainDyn(B, sel);
|
||||
host.declare(`declare ptr @scr_dyn_obj_read(ptr, ptr, ${host.sizeType})`);
|
||||
const r = B.tmp();
|
||||
B.line(`${r} = call ptr @scr_dyn_obj_read(ptr %d, ptr ${kParts.data}, ${host.sizeType} ${kParts.len})`);
|
||||
B.terminate(`ret ptr ${r}`);
|
||||
B.startBlock(lNext);
|
||||
}
|
||||
|
||||
@@ -2449,6 +2449,18 @@ class LlEmitter {
|
||||
const rc = vAdapters(this, t);
|
||||
this.declare(`declare void @scr_throw_obj(ptr, ptr, ptr, ptr)`);
|
||||
B.line(`call void @scr_throw_obj(ptr ${v.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(this, t)})`);
|
||||
} else if (t.kind === "symbol" || t.kind === "bigint" || t.kind === "func" || t.kind === "classval") {
|
||||
const rc = vAdapters(this, t);
|
||||
this.declare(`declare void @scr_throw_primitive_ref(ptr, ptr, ptr, ptr)`);
|
||||
B.line(`call void @scr_throw_primitive_ref(ptr ${v.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr null)`);
|
||||
} else if (t.kind === "dyn" || t.kind === "jsval") {
|
||||
const rc = vAdapters(this, t);
|
||||
const test = t.kind === "dyn" ? "scr_dyn_is_object" : "scr_jsval_is_object";
|
||||
this.declare(`declare zeroext i1 @${test}(ptr)`);
|
||||
this.declare(`declare void @scr_throw_ref_classified(ptr, ptr, ptr, ptr, i1 zeroext)`);
|
||||
const object = B.tmp();
|
||||
B.line(`${object} = call zeroext i1 @${test}(ptr ${v.name})`);
|
||||
B.line(`call void @scr_throw_ref_classified(ptr ${v.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(this, t)}, i1 zeroext ${object})`);
|
||||
} else {
|
||||
const rc = vAdapters(this, t);
|
||||
this.declare(`declare void @scr_throw_ref(ptr, ptr, ptr, ptr)`);
|
||||
|
||||
@@ -736,6 +736,15 @@ export function emitDynamicExpr(host: LlvmEmitterContext, e: ExprOf<"dynFrom" |
|
||||
B.line(`${n} = xor i1 ${t}, true`);
|
||||
return { name: n, type: e.type };
|
||||
}
|
||||
if (e.test === "object") {
|
||||
host.declare(`declare zeroext i1 @scr_caught_is_object(ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call zeroext i1 @scr_caught_is_object(ptr ${c.name})`);
|
||||
if (e.negated !== true) return { name: t, type: e.type };
|
||||
const n = B.tmp();
|
||||
B.line(`${n} = xor i1 ${t}, true`);
|
||||
return { name: n, type: e.type };
|
||||
}
|
||||
const tag = { string: 3, number: 1, boolean: 2 }[e.test];
|
||||
const kp = B.tmp();
|
||||
const k = B.tmp();
|
||||
|
||||
@@ -7065,9 +7065,8 @@ function loweredTemplateStrings(
|
||||
* becomes the catch binding, and a handler VALUE would need a
|
||||
* caught-typed closure parameter, which cannot exist. finally takes
|
||||
* any () => void closure (its callback sees no arguments). then takes
|
||||
* exactly one FULFILLMENT handler (any closure value of the settled
|
||||
* value's type — the two-argument onRejected form stays fenced toward
|
||||
* .catch); a promise-returning handler flattens through the async
|
||||
* a fulfillment handler and optionally a rejection handler; a
|
||||
* promise-returning handler flattens through the async
|
||||
* return path, a receiver rejection passes through untouched (the
|
||||
* wrapper's await re-throws it), and a handler throw rejects the
|
||||
* result — the spec's onFulfilled rules by construction. Null for
|
||||
@@ -7158,12 +7157,12 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
const passthrough =
|
||||
call.arguments.length === 0 ||
|
||||
(call.arguments.length === 1 && isAbsentHandler(call.arguments[0]));
|
||||
if (call.arguments.length !== 1 && !passthrough) {
|
||||
if (call.arguments.length !== 1 && !passthrough && !(member === "then" && call.arguments.length === 2)) {
|
||||
lowerer.noLowering(
|
||||
`${member} with ${call.arguments.length} arguments`,
|
||||
call,
|
||||
member === "then"
|
||||
? "the supported form takes exactly one fulfillment handler — chain .catch(...) for the rejection half"
|
||||
? "the supported form takes a fulfillment handler and an optional rejection handler"
|
||||
: `the supported form takes exactly one ${member === "catch" ? "inline handler" : "callback"}`,
|
||||
);
|
||||
}
|
||||
@@ -7238,6 +7237,132 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
}
|
||||
|
||||
if (member === "then") {
|
||||
if (call.arguments.length === 2) {
|
||||
const fulfilledNode = call.arguments[0]!;
|
||||
const rejectedNode = call.arguments[1]!;
|
||||
const fulfilledAbsent = isAbsentHandler(fulfilledNode);
|
||||
const rejectedAbsent = isAbsentHandler(rejectedNode);
|
||||
const resultT = lowerer.mapTypeOf(lowerer.typeOf(call));
|
||||
if (resultT?.kind !== "promise") {
|
||||
lowerer.noLowering("then with these handlers", call, "the result must be a representable promise");
|
||||
}
|
||||
const R = resultT.inner;
|
||||
if (!fulfilledAbsent && inner.kind === "jsval") markJsvalHandlerParams(lowerer, fulfilledNode);
|
||||
if (!fulfilledAbsent && inner.kind === "moduleNs") markModuleNsHandlerParams(lowerer, fulfilledNode, inner);
|
||||
const boxHandler = (node: ts.Expression, allowDirect: boolean): { value: IrExpr; direct: boolean; returnsPromise: boolean } => {
|
||||
const value = lowerer.lowerExpr(node);
|
||||
if (allowDirect && value.type.kind === "func" && value.type.params.length <= 1 &&
|
||||
(value.type.params.length === 0 || (inner.kind !== "void" && typeEquals(value.type.params[0]!, inner)))) {
|
||||
return { value, direct: true, returnsPromise: value.type.ret.kind === "promise" };
|
||||
}
|
||||
if (value.type.kind === "dyn") return { value, direct: false, returnsPromise: false };
|
||||
if (value.type.kind === "func" && value.type.params.length <= 1 &&
|
||||
canBoxFuncIntoDyn(value.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
|
||||
return { value: { kind: "dynFrom", value, type: DYN, loc }, direct: false, returnsPromise: value.type.ret.kind === "promise" };
|
||||
}
|
||||
lowerer.unsupported("SC1090", node, "then handlers that cannot be called with one value");
|
||||
};
|
||||
// Evaluate both callback expressions before the wrapper runs, in source order.
|
||||
const fulfilled = fulfilledAbsent ? null : boxHandler(fulfilledNode, true);
|
||||
const rejected = rejectedAbsent ? null : boxHandler(rejectedNode, false);
|
||||
const fnName = `%fn${lowerer.lambdaCounter++}_then2`;
|
||||
const paramTypes: IrType[] = [promT, ...(fulfilled ? [fulfilled.value.type] : []), ...(rejected ? [DYN] : [])];
|
||||
const funcType: IrType & { kind: "func" } = { kind: "func", params: paramTypes, ret: resultT };
|
||||
const fnCtx = newFnCtx(true, null, funcType, R);
|
||||
fnCtx.isAsync = true;
|
||||
lowerer.fnStack.push(fnCtx);
|
||||
try {
|
||||
const pLocal = lowerer.declareHiddenLocal("p", promT);
|
||||
const fLocal = fulfilled ? lowerer.declareHiddenLocal("f", fulfilled.value.type) : null;
|
||||
const rLocal = rejected ? lowerer.declareHiddenLocal("r", DYN) : null;
|
||||
const eLocal = lowerer.declareHiddenLocal("e", CAUGHT);
|
||||
const vLocal = inner.kind === "void" ? null : lowerer.declareHiddenLocal("v", inner);
|
||||
const awaitE: IrExpr = {
|
||||
kind: "awaitExpr",
|
||||
value: { kind: "varRef", localId: pLocal.id, type: promT, loc },
|
||||
type: inner,
|
||||
loc,
|
||||
};
|
||||
const tryBody: IrStmt[] = vLocal
|
||||
? [{ kind: "varDecl", localId: vLocal.id, init: awaitE, loc }]
|
||||
: [{ kind: "exprStmt", expr: awaitE, loc }];
|
||||
const resultOf = (value: IrExpr, returnsPromise = false): IrStmt[] => {
|
||||
const settled: IrExpr = value.type.kind === "promise"
|
||||
? { kind: "awaitExpr", value, type: value.type.inner, loc }
|
||||
: returnsPromise
|
||||
? { kind: "libCall", fn: "async.awaitDyn", args: [value], type: DYN, loc }
|
||||
: value;
|
||||
return R.kind === "void"
|
||||
? [{ kind: "exprStmt", expr: settled, loc }, { kind: "return", value: null, loc }]
|
||||
: [{ kind: "return", value: lowerer.coerceInto(call, settled, R), loc }];
|
||||
};
|
||||
const rejectedCall: IrExpr | null = rLocal ? {
|
||||
kind: "dynCall",
|
||||
callee: { kind: "varRef", localId: rLocal.id, type: DYN, loc },
|
||||
calleeName: jsFuncNameOf(rejectedNode) ?? "onRejected",
|
||||
args: [{ kind: "caughtToDyn", value: { kind: "varRef", localId: eLocal.id, type: CAUGHT, loc }, type: DYN, loc }],
|
||||
type: DYN,
|
||||
loc,
|
||||
} : null;
|
||||
const catchBody: IrStmt[] = rejectedCall
|
||||
? resultOf(rejectedCall, rejected?.returnsPromise)
|
||||
: [{ kind: "rethrow", localId: eLocal.id, loc }];
|
||||
const body: IrStmt[] = [{ kind: "tryCatch", tryBody, catchBody, catchLocalId: eLocal.id, finallyBody: null, loc }];
|
||||
// The fulfillment callback sits after the catch so a throw from it
|
||||
// rejects the result instead of reaching onRejected.
|
||||
if (fLocal) {
|
||||
const settled: IrExpr = vLocal
|
||||
? { kind: "varRef", localId: vLocal.id, type: inner, loc }
|
||||
: dynUndefinedExpr(loc);
|
||||
if (fulfilled!.direct && fLocal.type.kind === "func") {
|
||||
body.push(...resultOf({
|
||||
kind: "callValue",
|
||||
callee: { kind: "varRef", localId: fLocal.id, type: fLocal.type, loc },
|
||||
args: fLocal.type.params.length === 0 ? [] : [settled],
|
||||
type: fLocal.type.ret,
|
||||
loc,
|
||||
}));
|
||||
} else {
|
||||
const arg: IrExpr = settled.type.kind === "dyn" ? settled
|
||||
: canConvertToDyn(settled.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))
|
||||
? { kind: "dynFrom", value: settled, type: DYN, loc }
|
||||
: lowerer.unsupported("SC1090", fulfilledNode, "then fulfillment values that cannot cross the checked-dynamic boundary");
|
||||
body.push(...resultOf({
|
||||
kind: "dynCall",
|
||||
callee: { kind: "varRef", localId: fLocal.id, type: DYN, loc },
|
||||
calleeName: jsFuncNameOf(fulfilledNode) ?? "onFulfilled",
|
||||
args: [arg], type: DYN, loc,
|
||||
}, fulfilled?.returnsPromise));
|
||||
}
|
||||
} else if (vLocal) {
|
||||
body.push(...resultOf({ kind: "varRef", localId: vLocal.id, type: inner, loc }));
|
||||
} else if (R.kind === "void") {
|
||||
body.push({ kind: "return", value: null, loc });
|
||||
} else {
|
||||
body.push({ kind: "return", value: lowerer.coerceInto(call, dynUndefinedExpr(loc), R), loc });
|
||||
}
|
||||
const ctx = lowerer.ctx;
|
||||
const lifted: IrFunction = {
|
||||
name: fnName,
|
||||
params: [
|
||||
{ localId: pLocal.id, name: pLocal.name, type: promT },
|
||||
...(fLocal ? [{ localId: fLocal.id, name: fLocal.name, type: fLocal.type }] : []),
|
||||
...(rLocal ? [{ localId: rLocal.id, name: rLocal.name, type: DYN }] : []),
|
||||
],
|
||||
returnType: R,
|
||||
locals: ctx.locals,
|
||||
captures: ctx.captures!,
|
||||
body,
|
||||
loc,
|
||||
async: true,
|
||||
};
|
||||
lowerer.liftedFns.push(lifted);
|
||||
const closure: IrExpr = { kind: "closure", fnName, captures: ctx.captureSources, type: funcType, loc };
|
||||
return { kind: "callValue", callee: closure, args: [receiver, ...(fulfilled ? [fulfilled.value] : []), ...(rejected ? [rejected.value] : [])], type: resultT, loc };
|
||||
} finally {
|
||||
lowerer.fnStack.pop();
|
||||
}
|
||||
}
|
||||
// The settled value is an island HANDLE: an inline handler's
|
||||
// unannotated parameter binds it as jsval, whatever the checker's
|
||||
// contextual type spelled (a module-namespace type has no mapping —
|
||||
|
||||
@@ -7104,7 +7104,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
|
||||
"'typeof' catch-binding tests against non-literal strings",
|
||||
);
|
||||
}
|
||||
if (b.text === "string" || b.text === "number" || b.text === "boolean") {
|
||||
if (b.text === "string" || b.text === "number" || b.text === "boolean" || b.text === "object") {
|
||||
return {
|
||||
kind: "caughtTest",
|
||||
value: { kind: "varRef", localId: local.id, type: CAUGHT, loc },
|
||||
@@ -7117,8 +7117,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
b,
|
||||
`'typeof' catch-binding tests against "${b.text}" (only "string"/"number"/"boolean" ` +
|
||||
`narrow a catch binding; use 'instanceof' for objects)`,
|
||||
`'typeof' catch-binding tests against "${b.text}" (only "string"/"number"/"boolean"/"object" are supported)`,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -5359,14 +5359,15 @@ export type IrExpr =
|
||||
| { kind: "unionFuncEq"; unionId: string; tag: number; union: IrExpr; func: IrExpr; negated: boolean; type: IrType; loc: SrcLoc }
|
||||
/** Runtime test on a catch binding (`value` is a caught-typed varRef,
|
||||
* borrowed). The primitive tests ("string"/"number"/"boolean") compare
|
||||
* the snapshot's kind tag — exactly what `typeof e === "..."` observes;
|
||||
* the snapshot's kind tag. "object" also checks reference payloads,
|
||||
* excluding callable and primitive references;
|
||||
* "instanceof" requires `className` (a hierarchy class) and tests an OBJ
|
||||
* payload's vtable preorder against its interval (false for every other
|
||||
* payload kind). `negated` flips the result (the `!==` spelling). */
|
||||
| {
|
||||
kind: "caughtTest";
|
||||
value: IrExpr;
|
||||
test: "string" | "number" | "boolean" | "instanceof";
|
||||
test: "string" | "number" | "boolean" | "object" | "instanceof";
|
||||
className?: string;
|
||||
negated?: boolean;
|
||||
type: IrType;
|
||||
@@ -6694,7 +6695,9 @@ export function moduleUsesDynAsync(mod: IrModule): boolean {
|
||||
// A DYN-typed await reads through scr_await_dyn (the checked-dynamic tree-crossing
|
||||
// await lives in the gated TU) — promise<dyn> receivers' awaits and
|
||||
// the lifted then/catch helpers alike.
|
||||
if (node.kind === "awaitExpr" && node.type !== undefined && node.type.kind === "dyn") {
|
||||
const boxed = (node as { value?: { type?: { kind?: unknown; ret?: { kind?: unknown } } } }).value?.type;
|
||||
if ((node.kind === "awaitExpr" && node.type?.kind === "dyn") ||
|
||||
(node.kind === "dynFrom" && (boxed?.kind === "promise" || (boxed?.kind === "func" && boxed.ret?.kind === "promise")))) {
|
||||
found = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,10 @@ test.each([
|
||||
if (!built.ok) continue;
|
||||
const native = await exec(outPath);
|
||||
expect(native.stdout).toBe(node.stdout);
|
||||
expect(native.stderr).toBe(node.stderr);
|
||||
const stderr = sanitize
|
||||
? native.stderr.replace(/^==\d+==WARNING: ASan doesn't fully support makecontext\/swapcontext functions and may produce false positives in some cases!\n/gm, "")
|
||||
: native.stderr;
|
||||
expect(stderr).toBe(node.stderr);
|
||||
}
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
|
||||
@@ -6925,6 +6925,18 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3001-caught-typeof-object.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3001-caught-typeof-object.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3002-promise-then-two.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3002-promise-then-two.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/301-while.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/301-while.ts"
|
||||
@@ -7201,6 +7213,18 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3060-dynamic-host-global.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3060-dynamic-host-global.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3061-concurrent-fiber-stacks.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3061-concurrent-fiber-stacks.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3062-runtime-optional-record-guards.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3062-runtime-optional-record-guards.ts"
|
||||
@@ -7246,6 +7270,13 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3068-debug-variables/main.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3068-debug-variables/other.ts",
|
||||
"<repo>/tests/corpus/3068-debug-variables/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/400-fib.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/400-fib.ts"
|
||||
|
||||
@@ -230,7 +230,8 @@ static void scr_promise_trace(void *o, ScrTraceVisit visit, void *ctx) {
|
||||
/* Waiters are fibers (not refcounted objects) — the settled payload and
|
||||
* the combinator destinations are the strong references this promise
|
||||
* owns (destination promises are cycle-headered). */
|
||||
if ((p->payload_kind == SCR_EXC_REF || p->payload_kind == SCR_EXC_OBJ) &&
|
||||
if ((p->payload_kind == SCR_EXC_REF || p->payload_kind == SCR_EXC_OBJ ||
|
||||
p->payload_kind == SCR_EXC_PRIMITIVE_REF) &&
|
||||
p->trace_fn) {
|
||||
visit(p->payload, ctx);
|
||||
}
|
||||
@@ -244,7 +245,8 @@ static void scr_promise_gcfree(void *o) {
|
||||
ScrPromise *p = (ScrPromise *)o;
|
||||
if (p->payload_kind == SCR_EXC_STR) {
|
||||
scr_str_release((ScrStr *)p->payload);
|
||||
} else if ((p->payload_kind == SCR_EXC_REF || p->payload_kind == SCR_EXC_OBJ) &&
|
||||
} else if ((p->payload_kind == SCR_EXC_REF || p->payload_kind == SCR_EXC_OBJ ||
|
||||
p->payload_kind == SCR_EXC_PRIMITIVE_REF) &&
|
||||
p->payload && !p->trace_fn) {
|
||||
p->release_fn(p->payload);
|
||||
}
|
||||
@@ -313,7 +315,8 @@ ScrPromise *scr_promise_retain(ScrPromise *p) {
|
||||
|
||||
static void scr_promise_release_payload(ScrPromise *p) {
|
||||
if (p->payload_kind == SCR_EXC_STR) scr_str_release((ScrStr *)p->payload);
|
||||
else if ((p->payload_kind == SCR_EXC_REF || p->payload_kind == SCR_EXC_OBJ) &&
|
||||
else if ((p->payload_kind == SCR_EXC_REF || p->payload_kind == SCR_EXC_OBJ ||
|
||||
p->payload_kind == SCR_EXC_PRIMITIVE_REF) &&
|
||||
p->payload) p->release_fn(p->payload);
|
||||
p->payload_kind = SCR_EXC_NONE;
|
||||
p->payload = NULL;
|
||||
@@ -1011,7 +1014,8 @@ static void scr_promise_settle_from(ScrPromise *dst, ScrPromise *src) {
|
||||
dst->payload = NULL;
|
||||
if (src->payload_kind == SCR_EXC_STR && src->payload) {
|
||||
dst->payload = scr_str_retain((ScrStr *)src->payload);
|
||||
} else if ((src->payload_kind == SCR_EXC_REF || src->payload_kind == SCR_EXC_OBJ) &&
|
||||
} else if ((src->payload_kind == SCR_EXC_REF || src->payload_kind == SCR_EXC_OBJ ||
|
||||
src->payload_kind == SCR_EXC_PRIMITIVE_REF) &&
|
||||
src->payload) {
|
||||
dst->payload = src->retain_fn(src->payload);
|
||||
}
|
||||
@@ -1488,6 +1492,7 @@ static void scr_promise_rethrow(ScrPromise *p) {
|
||||
case SCR_EXC_BOOL: scr_throw_bool(p->b); break;
|
||||
case SCR_EXC_STR: scr_throw_str(scr_str_retain((ScrStr *)p->payload)); break;
|
||||
case SCR_EXC_REF: scr_throw_ref(p->retain_fn(p->payload), p->retain_fn, p->release_fn, p->trace_fn); break;
|
||||
case SCR_EXC_PRIMITIVE_REF: scr_throw_primitive_ref(p->retain_fn(p->payload), p->retain_fn, p->release_fn, p->trace_fn); break;
|
||||
case SCR_EXC_OBJ: scr_throw_obj(p->retain_fn(p->payload), p->retain_fn, p->release_fn, p->trace_fn); break;
|
||||
case SCR_EXC_NONE:
|
||||
case SCR_EXC_GENRET: /* unreachable: the sentinel never settles a promise */
|
||||
@@ -3239,7 +3244,8 @@ static void scr_gen_slot_reset(ScrGenSlot *s) {
|
||||
static void scr_gen_exc_reset(ScrExcCell *cell) {
|
||||
if (cell->kind == SCR_EXC_STR) {
|
||||
scr_str_release((ScrStr *)cell->payload);
|
||||
} else if (cell->kind == SCR_EXC_REF || cell->kind == SCR_EXC_OBJ) {
|
||||
} else if (cell->kind == SCR_EXC_REF || cell->kind == SCR_EXC_OBJ ||
|
||||
cell->kind == SCR_EXC_PRIMITIVE_REF) {
|
||||
cell->release_fn(cell->payload);
|
||||
}
|
||||
memset(cell, 0, sizeof *cell);
|
||||
|
||||
@@ -616,6 +616,7 @@ ScrDyn *scr_caught_to_dyn(const ScrCaught *c) {
|
||||
case SCR_EXC_BOOL: return scr_dyn_new_bool(c->b);
|
||||
case SCR_EXC_STR: return scr_dyn_new_str((ScrStr *)c->payload);
|
||||
case SCR_EXC_REF:
|
||||
case SCR_EXC_PRIMITIVE_REF:
|
||||
if (c->retain_fn == scr_dyn_retain_v) return scr_dyn_retain((ScrDyn *)c->payload);
|
||||
return scr_dyn_new_obj();
|
||||
case SCR_EXC_OBJ:
|
||||
@@ -665,7 +666,8 @@ ScrDyn *scr_promise_reason_dyn(const ScrPromise *p) {
|
||||
scr_str_release(v);
|
||||
return d;
|
||||
}
|
||||
case SCR_EXC_REF: {
|
||||
case SCR_EXC_REF:
|
||||
case SCR_EXC_PRIMITIVE_REF: {
|
||||
void *v = scr_promise_payload_ref((ScrPromise *)p);
|
||||
if (v == NULL) return scr_dyn_new_obj();
|
||||
if (scr_promise_payload_is_dyn(p)) return (ScrDyn *)v; /* retained */
|
||||
|
||||
@@ -75,7 +75,8 @@ bool scr_exc_pending(void) { return scr_exc_kind != SCR_EXC_NONE; }
|
||||
static void scr_exc_reset(void) {
|
||||
if (scr_exc_kind == SCR_EXC_STR) {
|
||||
scr_str_release((ScrStr *)scr_exc_payload);
|
||||
} else if (scr_exc_kind == SCR_EXC_REF || scr_exc_kind == SCR_EXC_OBJ) {
|
||||
} else if (scr_exc_kind == SCR_EXC_REF || scr_exc_kind == SCR_EXC_OBJ ||
|
||||
scr_exc_kind == SCR_EXC_PRIMITIVE_REF) {
|
||||
scr_exc_release_fn(scr_exc_payload);
|
||||
}
|
||||
scr_exc_kind = SCR_EXC_NONE;
|
||||
@@ -121,7 +122,8 @@ void scr_caught_release(ScrCaught *c) {
|
||||
if (--c->rc == 0) {
|
||||
if (c->kind == SCR_EXC_STR) {
|
||||
scr_str_release((ScrStr *)c->payload);
|
||||
} else if (c->kind == SCR_EXC_REF || c->kind == SCR_EXC_OBJ) {
|
||||
} else if (c->kind == SCR_EXC_REF || c->kind == SCR_EXC_OBJ ||
|
||||
c->kind == SCR_EXC_PRIMITIVE_REF) {
|
||||
c->release_fn(c->payload);
|
||||
}
|
||||
scr_obj_free_note();
|
||||
@@ -137,6 +139,9 @@ void scr_rethrow(const ScrCaught *c) {
|
||||
case SCR_EXC_REF:
|
||||
scr_throw_ref(c->retain_fn(c->payload), c->retain_fn, c->release_fn, c->trace_fn);
|
||||
break;
|
||||
case SCR_EXC_PRIMITIVE_REF:
|
||||
scr_throw_primitive_ref(c->retain_fn(c->payload), c->retain_fn, c->release_fn, c->trace_fn);
|
||||
break;
|
||||
case SCR_EXC_OBJ:
|
||||
scr_throw_obj(c->retain_fn(c->payload), c->retain_fn, c->release_fn, c->trace_fn);
|
||||
break;
|
||||
@@ -183,6 +188,10 @@ bool scr_caught_instanceof(const ScrCaught *c, size_t pre, size_t post) {
|
||||
return pre <= vt->pre && vt->pre <= post;
|
||||
}
|
||||
|
||||
bool scr_caught_is_object(const ScrCaught *c) {
|
||||
return c->kind == SCR_EXC_OBJ || c->kind == SCR_EXC_REF;
|
||||
}
|
||||
|
||||
ScrStr *scr_caught_to_string(const ScrCaught *c) {
|
||||
switch (c->kind) {
|
||||
case SCR_EXC_F64: return scr_f64_to_scrstr(c->f64);
|
||||
@@ -198,6 +207,7 @@ ScrStr *scr_caught_to_string(const ScrCaught *c) {
|
||||
}
|
||||
/* fall through */
|
||||
case SCR_EXC_REF:
|
||||
case SCR_EXC_PRIMITIVE_REF:
|
||||
/* Object.prototype.toString — exact for thrown records and class
|
||||
* instances without a toString override; thrown arrays/closures/union
|
||||
* boxes print this too where Node would vary (SEMANTICS.md). */
|
||||
@@ -275,7 +285,8 @@ void scr_library_check_exc(void) {
|
||||
break;
|
||||
}
|
||||
/* fall through: non-Error hierarchy objects render like other refs */
|
||||
case SCR_EXC_REF: {
|
||||
case SCR_EXC_REF:
|
||||
case SCR_EXC_PRIMITIVE_REF: {
|
||||
const char obj[] = "[object]";
|
||||
memcpy(buf + n, obj, sizeof obj - 1);
|
||||
n += sizeof obj - 1;
|
||||
@@ -324,6 +335,18 @@ void scr_throw_ref(void *v, void *(*retain)(void *), void (*release)(void *),
|
||||
scr_exc_trace_fn = trace;
|
||||
}
|
||||
|
||||
void scr_throw_primitive_ref(void *v, void *(*retain)(void *), void (*release)(void *),
|
||||
ScrTraceFn trace) {
|
||||
scr_throw_ref(v, retain, release, trace);
|
||||
scr_exc_kind = SCR_EXC_PRIMITIVE_REF;
|
||||
}
|
||||
|
||||
void scr_throw_ref_classified(void *v, void *(*retain)(void *), void (*release)(void *),
|
||||
ScrTraceFn trace, bool object) {
|
||||
scr_throw_ref(v, retain, release, trace);
|
||||
if (!object) scr_exc_kind = SCR_EXC_PRIMITIVE_REF;
|
||||
}
|
||||
|
||||
void scr_throw_obj(void *v, void *(*retain)(void *), void (*release)(void *),
|
||||
ScrTraceFn trace) {
|
||||
scr_throw_ref(v, retain, release, trace);
|
||||
@@ -372,6 +395,7 @@ void scr_exc_print_uncaught(void) {
|
||||
}
|
||||
/* fall through: non-Error hierarchy objects render like other refs */
|
||||
case SCR_EXC_REF:
|
||||
case SCR_EXC_PRIMITIVE_REF:
|
||||
fputs("[object]", stderr);
|
||||
break;
|
||||
case SCR_EXC_NONE: /* main only calls this when pending */
|
||||
|
||||
@@ -376,8 +376,9 @@ static void sf_throw_dyn_reason(ScrDyn *reason) {
|
||||
scr_throw_str(scr_str_retain(reason->v.str));
|
||||
return;
|
||||
default:
|
||||
scr_throw_ref(scr_dyn_retain(reason ? reason : scr_dyn_undefined()),
|
||||
&scr_dyn_retain_v, &scr_dyn_release_v, NULL);
|
||||
scr_throw_ref_classified(scr_dyn_retain(reason ? reason : scr_dyn_undefined()),
|
||||
&scr_dyn_retain_v, &scr_dyn_release_v, NULL,
|
||||
scr_dyn_is_object(reason ? reason : scr_dyn_undefined()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1471,6 +1471,13 @@ ScrStr *scr_jsval_typeof(ScrJsval *a) {
|
||||
return s;
|
||||
}
|
||||
|
||||
bool scr_jsval_is_object(ScrJsval *a) {
|
||||
ScrStr *type = scr_jsval_typeof(a);
|
||||
bool object = type->len == 6 && memcmp(type->data, "object", 6) == 0;
|
||||
scr_str_release(type);
|
||||
return object;
|
||||
}
|
||||
|
||||
ScrStr *scr_jsval_to_str(ScrJsval *a) {
|
||||
isl_entry();
|
||||
return isl_js_to_str(a->v); /* NULL = bridged (e.g. a symbol) */
|
||||
|
||||
@@ -1387,6 +1387,13 @@ ScrStr *scr_dyn_typeof(const ScrDyn *d) {
|
||||
return scr_str_new(s, strlen(s));
|
||||
}
|
||||
|
||||
bool scr_dyn_is_object(const ScrDyn *d) {
|
||||
ScrStr *type = scr_dyn_typeof(d);
|
||||
bool object = type->len == 6 && memcmp(type->data, "object", 6) == 0;
|
||||
scr_str_release(type);
|
||||
return object;
|
||||
}
|
||||
|
||||
ScrStr *scr_dyn_object_tag(const ScrDyn *d) {
|
||||
if (d->kind == SCR_DYN_TYPED_REF) {
|
||||
if (scr_dyn_isl_is_error(d)) return scr_str_new("[object Error]", 14);
|
||||
@@ -2959,6 +2966,30 @@ bool scr_dyn_err_instanceof(const ScrDyn *d, double kind) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_obj_read(const ScrDyn *d, const char *key, size_t key_len) {
|
||||
ScrDyn *own = scr_dyn_obj_get(d, key, key_len);
|
||||
if (own) return scr_dyn_retain(own);
|
||||
if (key_len == 11 && memcmp(key, "constructor", 11) == 0) {
|
||||
static const char *const tokens[] = {
|
||||
"[builtin Error]", "[builtin TypeError]",
|
||||
"[builtin RangeError]", "[builtin SyntaxError]",
|
||||
};
|
||||
for (size_t i = 0; i < scr_errdyn_n; i++) {
|
||||
if (scr_errdyn_cache[i].dyn != d) continue;
|
||||
const ScrVt *vt = scr_errdyn_cache[i].err->vt;
|
||||
for (size_t kind = 0; kind < sizeof tokens / sizeof tokens[0]; kind++) {
|
||||
if (vt != &scr_error_vts[kind]) continue;
|
||||
ScrStr *token = scr_str_new(tokens[kind], strlen(tokens[kind]));
|
||||
ScrDyn *result = scr_dyn_new_str(token);
|
||||
scr_str_release(token);
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
/* ── Object.keys/values/entries over the checked-dynamic tree ──────────────────────────
|
||||
* JS own-key order: array-index keys ascending first, then the rest in
|
||||
* insertion order. entries answers [key, value] pairs; values RETAIN
|
||||
|
||||
@@ -2089,6 +2089,10 @@ typedef enum {
|
||||
* Carries NO payload (the value rides the generator's ret slot), so
|
||||
* scr_exc_clear/reset need no arm. Never escapes a generator fiber. */
|
||||
SCR_EXC_GENRET,
|
||||
/* Refcounted values whose JS typeof is not object (Symbol, BigInt,
|
||||
* functions, and checked-dynamic primitives). The payload uses the
|
||||
* same ownership as REF. */
|
||||
SCR_EXC_PRIMITIVE_REF,
|
||||
} ScrExcKind;
|
||||
|
||||
/* One cell per fiber (JS has one exception in flight per execution
|
||||
@@ -2119,7 +2123,12 @@ void scr_throw_str(ScrStr *v); /* takes ownership */
|
||||
* passes the payload type's `_v` adapters, like scr_union_new_ref); trace
|
||||
* is non-NULL iff the payload type carries a cycle header. */
|
||||
void scr_throw_ref(void *v, void *(*retain)(void *), void (*release)(void *),
|
||||
ScrTraceFn trace);
|
||||
ScrTraceFn trace);
|
||||
/* Store a reference whose JS typeof was checked at the throw site. */
|
||||
void scr_throw_ref_classified(void *v, void *(*retain)(void *), void (*release)(void *),
|
||||
ScrTraceFn trace, bool object);
|
||||
void scr_throw_primitive_ref(void *v, void *(*retain)(void *), void (*release)(void *),
|
||||
ScrTraceFn trace);
|
||||
/* Same ownership contract as scr_throw_ref; the payload must be a
|
||||
* hierarchy-class instance (vtable word present) — see SCR_EXC_OBJ. */
|
||||
void scr_throw_obj(void *v, void *(*retain)(void *), void (*release)(void *),
|
||||
@@ -2162,6 +2171,9 @@ void scr_rethrow(const ScrCaught *c);
|
||||
/* `e instanceof C` on a catch binding: an OBJ payload whose vtable preorder
|
||||
* lies inside C's interval. False for every other payload kind. */
|
||||
bool scr_caught_instanceof(const ScrCaught *c, size_t pre, size_t post);
|
||||
/* `typeof e === "object"` on a catch binding. Throw sites choose REF or
|
||||
* PRIMITIVE_REF from the value's JS typeof before storing the payload. */
|
||||
bool scr_caught_is_object(const ScrCaught *c);
|
||||
/* `String(e)` / `${e}` on a catch binding: JS's String() over the snapshot
|
||||
* — numbers/booleans/strings by value, Error payloads via
|
||||
* scr_error_to_string ("name: message" — String(e) carries no stack in
|
||||
@@ -3597,6 +3609,12 @@ double scr_dyn_number_coerce(const ScrDyn *d);
|
||||
* back to its runtime error and the class's stamped preorder interval
|
||||
* answers. A dyn value that never came from an error answers false. */
|
||||
bool scr_dyn_err_instanceof(const ScrDyn *d, double kind);
|
||||
/* Property read on a checked-dynamic object. Own fields take precedence;
|
||||
* runtime Error instances inherit their exact builtin constructor token.
|
||||
* The result is owned. */
|
||||
ScrDyn *scr_dyn_obj_read(const ScrDyn *d, const char *key, size_t key_len);
|
||||
/* JS typeof comparison, including null's "object" result. */
|
||||
bool scr_dyn_is_object(const ScrDyn *d);
|
||||
|
||||
/* structuredClone over the checked-dynamic tree: JSON-safe data + bytes deep-copy;
|
||||
* functions/handles throw the spec's catchable DataCloneError; cycles
|
||||
@@ -4821,6 +4839,7 @@ ScrJsval *scr_jsval_iter_new(ScrJsval *a);
|
||||
ScrJsval *scr_jsval_plus(ScrJsval *a); /* unary + (ToNumber) */
|
||||
int scr_jsval_truthy(ScrJsval *a);
|
||||
ScrStr *scr_jsval_typeof(ScrJsval *a);
|
||||
bool scr_jsval_is_object(ScrJsval *a);
|
||||
ScrStr *scr_jsval_to_str(ScrJsval *a); /* String(v); NULL = bridged */
|
||||
|
||||
/* Property/element access and calls. Names are NUL-terminated ScrStr
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
try {
|
||||
throw new TypeError("x");
|
||||
} catch (error) {
|
||||
console.log(typeof error === "object", typeof error !== "object");
|
||||
}
|
||||
|
||||
try {
|
||||
throw () => 1;
|
||||
} catch (error) {
|
||||
console.log(typeof error === "object", typeof error !== "object");
|
||||
}
|
||||
|
||||
try {
|
||||
throw 1;
|
||||
} catch (error) {
|
||||
console.log(typeof error === "object", typeof error !== "object");
|
||||
}
|
||||
|
||||
try {
|
||||
throw JSON.parse("null");
|
||||
} catch (error) {
|
||||
console.log(typeof error === "object", typeof error !== "object");
|
||||
}
|
||||
|
||||
try {
|
||||
throw Symbol("x");
|
||||
} catch (error) {
|
||||
console.log(typeof error === "object", typeof error !== "object");
|
||||
}
|
||||
|
||||
try {
|
||||
throw 1n;
|
||||
} catch (error) {
|
||||
console.log(typeof error === "object", typeof error !== "object");
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
function failing(): Promise<number> {
|
||||
return new Promise<number>((_resolve, reject) => reject(new Error("receiver")));
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const fulfilled = await Promise.resolve(2).then((value) => value + 1, () => 0);
|
||||
console.log("fulfilled", fulfilled);
|
||||
|
||||
const rejected = await failing().then(() => "unexpected", (error) => {
|
||||
if (error instanceof Error) return error.message;
|
||||
return "not an Error";
|
||||
});
|
||||
console.log("rejected", rejected);
|
||||
|
||||
const handlerThrow = await Promise.resolve("value")
|
||||
.then((): string => { throw new Error("handler"); }, () => "wrong handler")
|
||||
.catch((error) => {
|
||||
if (error instanceof Error) return error.message;
|
||||
return "not an Error";
|
||||
});
|
||||
console.log("handler throw", handlerThrow);
|
||||
|
||||
const flattened = await Promise.resolve(5).then(
|
||||
(value) => Promise.resolve(value + 1),
|
||||
() => Promise.resolve(0),
|
||||
);
|
||||
console.log("flattened", flattened);
|
||||
|
||||
const identity = await Promise.resolve(8).then(undefined, () => 0);
|
||||
console.log("identity", identity);
|
||||
|
||||
const passedRejection = await failing().then(() => "wrong", undefined).catch((error) => {
|
||||
if (error instanceof Error) return error.message;
|
||||
return "not an Error";
|
||||
});
|
||||
console.log("passed rejection", passedRejection);
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -1,6 +1,6 @@
|
||||
// Catch bindings are deliberately NARROW: the binding is typed by what the
|
||||
// exception cell can actually hold, so the supported uses are the narrowing
|
||||
// tests (instanceof over hierarchy classes, typeof over primitives), reads
|
||||
// tests (instanceof over hierarchy classes, typeof over selected types), reads
|
||||
// under a proven narrow, rethrow, and the unknown-slot CONVERSION (an
|
||||
// un-narrowed use typed `unknown` converts to a dynamic value — corpus
|
||||
// 1554; `const copy = e` compiles now and is NOT in this fence corpus).
|
||||
@@ -37,8 +37,8 @@ try {
|
||||
try {
|
||||
throw 6;
|
||||
} catch (e) {
|
||||
if (typeof e === "object") {
|
||||
// only the primitive typeof tests narrow
|
||||
if (typeof e === "function") {
|
||||
// callable catch tests remain outside the supported narrowing set
|
||||
console.log("no");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ catch-bindings.ts:35:3 - error SC1090: assignments to catch bindings are not sup
|
||||
| ^
|
||||
36 | }
|
||||
|
||||
catch-bindings.ts:40:20 - error SC1090: 'typeof' catch-binding tests against "object" (only "string"/"number"/"boolean" narrow a catch binding; use 'instanceof' for objects) is not supported yet
|
||||
catch-bindings.ts:40:20 - error SC1090: 'typeof' catch-binding tests against "function" (only "string"/"number"/"boolean"/"object" are supported) is not supported yet
|
||||
|
||||
39 | } catch (e) {
|
||||
40 | if (typeof e === "object") {
|
||||
| ^~~~~~~~
|
||||
41 | // only the primitive typeof tests narrow
|
||||
40 | if (typeof e === "function") {
|
||||
| ^~~~~~~~~~
|
||||
41 | // callable catch tests remain outside the supported narrowing set
|
||||
@@ -258,15 +258,6 @@ stdlib-fence.ts:61:16 - error SC2020: 'JSON.stringify with replacer/space parame
|
||||
|
||||
hint: the serializer is type-directed — shape the value before stringifying
|
||||
|
||||
stdlib-fence.ts:66:16 - error SC2020: 'then with 2 arguments' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
65 | }
|
||||
66 | const thened = work().then((v) => v + 1, () => 0);
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
67 | const caught = work().catch(() => 0);
|
||||
|
||||
hint: the supported form takes exactly one fulfillment handler — chain .catch(...) for the rejection half
|
||||
|
||||
stdlib-fence.ts:73:15 - error SC2020: 'Object.is over a dynamic operand' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
72 | // unions needing narrowing.
|
||||
|
||||
@@ -32,9 +32,9 @@ Fetching aliases under mock-team
|
||||
`;
|
||||
|
||||
exports[`vercel e2e (real CLI vs Node against the mock api) > alias ls > alias ls stdout 1`] = `
|
||||
" source url age
|
||||
mock-app-abc123defg-mock-team.vercel.app mock-app.vercel.app <T>
|
||||
mock-app-abc123defg-mock-team.vercel.app mock-example.com <T>
|
||||
" source url age
|
||||
mock-app-abc123defg-mock-team.vercel.app mock-app.vercel.app <T>
|
||||
mock-app-abc123defg-mock-team.vercel.app mock-example.com <T>
|
||||
|
||||
"
|
||||
`;
|
||||
@@ -95,9 +95,9 @@ exports[`vercel e2e (real CLI vs Node against the mock api) > certs ls > certs l
|
||||
`;
|
||||
|
||||
exports[`vercel e2e (real CLI vs Node against the mock api) > certs ls > certs ls stdout 1`] = `
|
||||
" id cns expiration renew age
|
||||
cert_mock000000000000000000001 - mock-example.com <T> ago yes <T>
|
||||
- *.mock-example.com
|
||||
" id cns expiration renew age
|
||||
cert_mock000000000000000000001 - mock-example.com <T> ago yes <T>
|
||||
- *.mock-example.com
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -134,11 +134,11 @@ exports[`vercel e2e (real CLI vs Node against the mock api) > dns ls > dns ls st
|
||||
"Vercel CLI 56.2.0 (Node.js <V>)
|
||||
Fetching domains under mock-team
|
||||
> Records found under mock-team [<T>]
|
||||
>
|
||||
>
|
||||
mock-example.com
|
||||
id name type value created
|
||||
A 76.76.21.21 default
|
||||
rec_mock0000000000000000000002 www CNAME cname.vercel-dns.com <T> ago
|
||||
id name type value created
|
||||
A 76.76.21.21 default
|
||||
rec_mock0000000000000000000002 www CNAME cname.vercel-dns.com <T> ago
|
||||
|
||||
"
|
||||
`;
|
||||
@@ -195,15 +195,15 @@ Fetching Domain mock-example.com under mock-team
|
||||
|
||||
Nameservers
|
||||
|
||||
Intended Nameservers Current Nameservers
|
||||
ns1.vercel-dns.com ns1.example-dns.com ✘
|
||||
ns2.vercel-dns.com ns2.example-dns.com ✘
|
||||
Intended Nameservers Current Nameservers
|
||||
ns1.vercel-dns.com ns1.example-dns.com ✘
|
||||
ns2.vercel-dns.com ns2.example-dns.com ✘
|
||||
|
||||
Projects
|
||||
|
||||
Project Domains
|
||||
mock-app mock-example.com
|
||||
|
||||
|
||||
Project Domains
|
||||
mock-app mock-example.com
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -223,10 +223,10 @@ exports[`vercel e2e (real CLI vs Node against the mock api) > domains ls > domai
|
||||
"Vercel CLI 56.2.0 (Node.js <V>)
|
||||
Fetching Domains under mock-team
|
||||
> 1 Domain found under mock-team [<T>]
|
||||
|
||||
Domain Registrar Nameservers Expiration Date Creator Age
|
||||
mock-example.com Third Party Third Party - mockuser <T>
|
||||
|
||||
|
||||
Domain Registrar Nameservers Expiration Date Creator Age
|
||||
mock-example.com Third Party Third Party - mockuser <T>
|
||||
|
||||
|
||||
"
|
||||
`;
|
||||
@@ -252,9 +252,9 @@ Retrieving project…
|
||||
Saving…
|
||||
|
||||
✓ Added MOCK_API_TOKEN
|
||||
Project mock-team/mock-app
|
||||
Environments Production
|
||||
Type Sensitive
|
||||
Project mock-team/mock-app
|
||||
Environments Production
|
||||
Type Sensitive
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -281,10 +281,10 @@ Retrieving project…
|
||||
|
||||
exports[`vercel e2e (real CLI vs Node against the mock api) > env ls > env ls stdout 1`] = `
|
||||
"
|
||||
name value environments (git branch) created
|
||||
DATABASE_URL Encrypted Production, Preview, Development <T> ago
|
||||
NEXT_PUBLIC_BASE_URL https://mock-examp… Production <T> ago
|
||||
PREVIEW_FLAG Encrypted Preview (staging) <T> ago
|
||||
name value environments (git branch) created
|
||||
DATABASE_URL Encrypted Production, Preview, Development <T> ago
|
||||
NEXT_PUBLIC_BASE_URL https://mock-examp… Production <T> ago
|
||||
PREVIEW_FLAG Encrypted Preview (staging) <T> ago
|
||||
|
||||
"
|
||||
`;
|
||||
@@ -370,9 +370,9 @@ Retrieving project…
|
||||
|
||||
exports[`vercel e2e (real CLI vs Node against the mock api) > env ls production > env ls production stdout 1`] = `
|
||||
"
|
||||
name value environments created
|
||||
DATABASE_URL Encrypted Production, Preview, Development <T> ago
|
||||
NEXT_PUBLIC_BASE_URL https://mock-examp… Production <T> ago
|
||||
name value environments created
|
||||
DATABASE_URL Encrypted Production, Preview, Development <T> ago
|
||||
NEXT_PUBLIC_BASE_URL https://mock-examp… Production <T> ago
|
||||
|
||||
"
|
||||
`;
|
||||
@@ -431,88 +431,88 @@ exports[`vercel e2e (real CLI vs Node against the mock api) > help > help stderr
|
||||
|
||||
Basic
|
||||
|
||||
deploy [path] Performs a deployment (default)
|
||||
build Build the project locally into './vercel/output'
|
||||
cache [cmd] Manages cache for your current Project
|
||||
dev Start a local development server
|
||||
env Manages the Environment Variables for your current Project
|
||||
git Manage Git provider repository for your current Project
|
||||
help [cmd] Displays complete help for [cmd]
|
||||
init [example] Initialize an example project
|
||||
inspect [id] Displays information related to a deployment
|
||||
i | install [name] Install an integration from the Marketplace
|
||||
integration [cmd] Manages your Marketplace integrations
|
||||
ir | integration-resource [cmd] Manages your Marketplace integration resources
|
||||
link [path] Link local directory to a Vercel Project
|
||||
ls | list [app] Lists deployments
|
||||
login [email] Logs into your account or creates a new one
|
||||
logout Logs out of your account
|
||||
open Opens the current project in the Vercel Dashboard
|
||||
promote [url|id] Promote an existing deployment to current
|
||||
pull [path] Pull your Project Settings from the cloud
|
||||
redeploy [url|id] Rebuild and deploy a previous deployment.
|
||||
rollback [url|id] Quickly revert back to a previous deployment
|
||||
switch [scope] Switches between different scopes
|
||||
deploy [path] Performs a deployment (default)
|
||||
build Build the project locally into './vercel/output'
|
||||
cache [cmd] Manages cache for your current Project
|
||||
dev Start a local development server
|
||||
env Manages the Environment Variables for your current Project
|
||||
git Manage Git provider repository for your current Project
|
||||
help [cmd] Displays complete help for [cmd]
|
||||
init [example] Initialize an example project
|
||||
inspect [id] Displays information related to a deployment
|
||||
i | install [name] Install an integration from the Marketplace
|
||||
integration [cmd] Manages your Marketplace integrations
|
||||
ir | integration-resource [cmd] Manages your Marketplace integration resources
|
||||
link [path] Link local directory to a Vercel Project
|
||||
ls | list [app] Lists deployments
|
||||
login [email] Logs into your account or creates a new one
|
||||
logout Logs out of your account
|
||||
open Opens the current project in the Vercel Dashboard
|
||||
promote [url|id] Promote an existing deployment to current
|
||||
pull [path] Pull your Project Settings from the cloud
|
||||
redeploy [url|id] Rebuild and deploy a previous deployment.
|
||||
rollback [url|id] Quickly revert back to a previous deployment
|
||||
switch [scope] Switches between different scopes
|
||||
|
||||
Advanced
|
||||
|
||||
activity List user activity events
|
||||
agent [init] Generate AGENTS.md with Vercel best practices
|
||||
ai-gateway [cmd] Manage AI Gateway resources
|
||||
agent-runs [cmd] Inspect Agent Runs observability data
|
||||
alerts List alerts for a project or team
|
||||
alias [cmd] Manages your domain aliases
|
||||
api [endpoint] Make authenticated HTTP requests to the Vercel API [beta]
|
||||
bisect Use binary search to find the deployment that introduced a bug
|
||||
blob [cmd] Manages your Blob stores and files
|
||||
buy [cmd] Purchase Vercel products for your team
|
||||
certs [cmd] Manages your SSL certificates
|
||||
connect [cmd] Manage connectors [beta]
|
||||
contract Show contract information for billing periods
|
||||
cron | crons [cmd] Manage cron jobs for a project [beta]
|
||||
curl [path] cURL requests to your linked project's deployment [beta]
|
||||
deploy-hooks [cmd] Manage deploy hooks for Git-triggered builds
|
||||
dns [name] Manages your DNS records
|
||||
domains [name] Manages your domain names
|
||||
edge-config [cmd] Manage Edge Config stores
|
||||
firewall [cmd] Manages Vercel Firewall configuration and custom rules
|
||||
flags [cmd] Manage feature flags for a Vercel project
|
||||
httpstat path Visualize HTTP timing statistics for deployments
|
||||
logs [url] Displays the logs for a deployment
|
||||
metrics <metric> Queries observability metrics for your project or team
|
||||
mcp Set up MCP agents and configuration
|
||||
microfrontends Manages your microfrontends
|
||||
oauth-apps [cmd] Register Vercel Apps (OAuth) and manage team installations
|
||||
projects Manages your Projects
|
||||
redirects [cmd] Manages redirects for your current Project
|
||||
rm | remove [id] Removes a deployment
|
||||
routes [cmd] Manages routing rules for your current Project
|
||||
rr | rolling-release [cmd] Manage rolling releases for gradual traffic shifting
|
||||
sandbox Interact with Vercel Sandbox
|
||||
skills [query] Discover agent skills relevant to your project
|
||||
target [cmd] Manage custom environments for your Project
|
||||
teams Manages your teams
|
||||
telemetry [cmd] Enable or disable telemetry collection
|
||||
tokens [cmd] Manage your personal Vercel authentication tokens
|
||||
traces [cmd] Fetch and capture traces for your project's deployment
|
||||
upgrade Upgrade the Vercel CLI to the latest version
|
||||
usage Show billing usage for the current billing period
|
||||
vcr [cmd] Manages your Container Registry repositories and images
|
||||
webhooks [cmd] Manages webhooks [beta]
|
||||
whoami Shows the username of the currently logged in user
|
||||
activity List user activity events
|
||||
agent [init] Generate AGENTS.md with Vercel best practices
|
||||
ai-gateway [cmd] Manage AI Gateway resources
|
||||
agent-runs [cmd] Inspect Agent Runs observability data
|
||||
alerts List alerts for a project or team
|
||||
alias [cmd] Manages your domain aliases
|
||||
api [endpoint] Make authenticated HTTP requests to the Vercel API [beta]
|
||||
bisect Use binary search to find the deployment that introduced a bug
|
||||
blob [cmd] Manages your Blob stores and files
|
||||
buy [cmd] Purchase Vercel products for your team
|
||||
certs [cmd] Manages your SSL certificates
|
||||
connect [cmd] Manage connectors [beta]
|
||||
contract Show contract information for billing periods
|
||||
cron | crons [cmd] Manage cron jobs for a project [beta]
|
||||
curl [path] cURL requests to your linked project's deployment [beta]
|
||||
deploy-hooks [cmd] Manage deploy hooks for Git-triggered builds
|
||||
dns [name] Manages your DNS records
|
||||
domains [name] Manages your domain names
|
||||
edge-config [cmd] Manage Edge Config stores
|
||||
firewall [cmd] Manages Vercel Firewall configuration and custom rules
|
||||
flags [cmd] Manage feature flags for a Vercel project
|
||||
httpstat path Visualize HTTP timing statistics for deployments
|
||||
logs [url] Displays the logs for a deployment
|
||||
metrics <metric> Queries observability metrics for your project or team
|
||||
mcp Set up MCP agents and configuration
|
||||
microfrontends Manages your microfrontends
|
||||
oauth-apps [cmd] Register Vercel Apps (OAuth) and manage team installations
|
||||
projects Manages your Projects
|
||||
redirects [cmd] Manages redirects for your current Project
|
||||
rm | remove [id] Removes a deployment
|
||||
routes [cmd] Manages routing rules for your current Project
|
||||
rr | rolling-release [cmd] Manage rolling releases for gradual traffic shifting
|
||||
sandbox Interact with Vercel Sandbox
|
||||
skills [query] Discover agent skills relevant to your project
|
||||
target [cmd] Manage custom environments for your Project
|
||||
teams Manages your teams
|
||||
telemetry [cmd] Enable or disable telemetry collection
|
||||
tokens [cmd] Manage your personal Vercel authentication tokens
|
||||
traces [cmd] Fetch and capture traces for your project's deployment
|
||||
upgrade Upgrade the Vercel CLI to the latest version
|
||||
usage Show billing usage for the current billing period
|
||||
vcr [cmd] Manages your Container Registry repositories and images
|
||||
webhooks [cmd] Manages webhooks [beta]
|
||||
whoami Shows the username of the currently logged in user
|
||||
|
||||
Global Options:
|
||||
|
||||
-h, --help Output usage information
|
||||
-v, --version Output the version number
|
||||
--cwd Current working directory
|
||||
-A FILE, --local-config=FILE Path to the local \`vercel.json\` file
|
||||
-Q DIR, --global-config=DIR Path to the global \`.vercel\` directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
--non-interactive Run without interactive prompts (default when agent detected)
|
||||
-S, --scope Set a custom scope
|
||||
-t TOKEN, --token=TOKEN Login token
|
||||
-h, --help Output usage information
|
||||
-v, --version Output the version number
|
||||
--cwd Current working directory
|
||||
-A FILE, --local-config=FILE Path to the local \`vercel.json\` file
|
||||
-Q DIR, --global-config=DIR Path to the global \`.vercel\` directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
--non-interactive Run without interactive prompts (default when agent detected)
|
||||
-S, --scope Set a custom scope
|
||||
-t TOKEN, --token=TOKEN Login token
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -549,29 +549,29 @@ exports[`vercel e2e (real CLI vs Node against the mock api) > help alias > help
|
||||
|
||||
▲ vercel alias [command]
|
||||
|
||||
Interact with deployment aliases
|
||||
Interact with deployment aliases
|
||||
|
||||
Commands:
|
||||
|
||||
list Show all aliases
|
||||
remove alias Remove an alias using its hostname
|
||||
set id-or-url alias Create a new alias (default)
|
||||
list Show all aliases
|
||||
remove alias Remove an alias using its hostname
|
||||
set id-or-url alias Create a new alias (default)
|
||||
|
||||
|
||||
Global Options:
|
||||
|
||||
--cwd <DIR> Sets the current working directory for a single
|
||||
run of a command
|
||||
-d, --debug Debug mode (default off)
|
||||
-Q, --global-config <DIR> Path to the global \`.vercel\` directory
|
||||
-h, --help Output usage information
|
||||
-A, --local-config <FILE> Path to the local \`vercel.json\` file
|
||||
--no-color No color mode (default off)
|
||||
--non-interactive Run without interactive prompts; when an agent is
|
||||
detected this is the default
|
||||
-S, --scope Set a custom scope
|
||||
-t, --token <TOKEN> Login token
|
||||
-v, --version Output the version number
|
||||
--cwd <DIR> Sets the current working directory for a single
|
||||
run of a command
|
||||
-d, --debug Debug mode (default off)
|
||||
-Q, --global-config <DIR> Path to the global \`.vercel\` directory
|
||||
-h, --help Output usage information
|
||||
-A, --local-config <FILE> Path to the local \`vercel.json\` file
|
||||
--no-color No color mode (default off)
|
||||
--non-interactive Run without interactive prompts; when an agent is
|
||||
detected this is the default
|
||||
-S, --scope Set a custom scope
|
||||
-t, --token <TOKEN> Login token
|
||||
-v, --version Output the version number
|
||||
|
||||
|
||||
Examples:
|
||||
@@ -623,14 +623,14 @@ Fetching deployment "mock-app-abc123defg-mock-team.vercel.app" in mock-team
|
||||
|
||||
╶ https://mock-app.vercel.app
|
||||
╶ https://mock-example.com
|
||||
|
||||
|
||||
|
||||
Builds
|
||||
|
||||
┌ package.json [<T>]
|
||||
┌ package.json [<T>]
|
||||
├── λ api/index (1MB) [iad1]
|
||||
└── index.html (2KB)
|
||||
|
||||
|
||||
|
||||
"
|
||||
`;
|
||||
@@ -654,10 +654,10 @@ exports[`vercel e2e (real CLI vs Node against the mock api) > logs for a deploym
|
||||
Resolving deployment "mock-app-abc123defg-mock-team.vercel.app"
|
||||
Fetching project "prj_mock000000000000000000000001"
|
||||
Fetching logs...
|
||||
TIME HOST LEVEL STATUS MESSAGE
|
||||
00:01:40.00 mock-app.vercel.app info ◇ GET / 200 (no message)
|
||||
TIME HOST LEVEL STATUS MESSAGE
|
||||
00:01:40.00 mock-app.vercel.app info ◇ GET / 200 (no message)
|
||||
00:01:50.00 mock-app.vercel.app error λ GET /api/data 500 TypeError: Cannot read properties of undefine…
|
||||
00:02:00.00 mock-app.vercel.app warning λ POST /api/submit 202 payload missing optional field "tags"
|
||||
00:02:00.00 mock-app.vercel.app warning λ POST /api/submit 202 payload missing optional field "tags"
|
||||
Fetched 3 logs for mock-team/mock-app
|
||||
"
|
||||
`;
|
||||
@@ -679,9 +679,9 @@ exports[`vercel e2e (real CLI vs Node against the mock api) > ls (deployments) >
|
||||
Fetching deployments in mock-team
|
||||
> Deployments under mock-team [<T>]
|
||||
|
||||
Age Project Deployment Status Environment Duration Username
|
||||
<T> mock-team/mock-app https://mock-app-abc123defg-mock-team.vercel.app ● Ready Production <T> mockuser
|
||||
<T> mock-team/mock-app https://mock-app-hij456klmn-mock-team.vercel.app ● Error Preview ? mockuser
|
||||
Age Project Deployment Status Environment Duration Username
|
||||
<T> mock-team/mock-app https://mock-app-abc123defg-mock-team.vercel.app ● Ready Production <T> mockuser
|
||||
<T> mock-team/mock-app https://mock-app-hij456klmn-mock-team.vercel.app ● Error Preview ? mockuser
|
||||
|
||||
"
|
||||
`;
|
||||
@@ -725,9 +725,9 @@ exports[`vercel e2e (real CLI vs Node against the mock api) > project ls > proje
|
||||
Fetching projects in mock-team
|
||||
> Projects found under mock-team [<T>]
|
||||
|
||||
Project Name Latest Production URL Updated Node Version
|
||||
mock-app -- <T> 22.x
|
||||
static-site -- <T> 22.x
|
||||
Project Name Latest Production URL Updated Node Version
|
||||
mock-app -- <T> 22.x
|
||||
static-site -- <T> 22.x
|
||||
|
||||
"
|
||||
`;
|
||||
@@ -747,9 +747,9 @@ exports[`vercel e2e (real CLI vs Node against the mock api) > teams ls > teams l
|
||||
"Vercel CLI 56.2.0 (Node.js <V>)
|
||||
Fetching teams
|
||||
Fetching user information
|
||||
id Team name
|
||||
second-team Second Team
|
||||
mock-team Mock Team
|
||||
id Team name
|
||||
second-team Second Team
|
||||
mock-team Mock Team
|
||||
"
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { boundedRun } from "../test262/execute.js";
|
||||
import { compileFailureStatus, exclusion, metadata, prepare, summarize, variants } from "../test262/support.mjs";
|
||||
import { compileFailureStatus, exclusion, matchesParseNegative, metadata, prepare, summarize, variants } from "../test262/support.mjs";
|
||||
|
||||
const source = (yaml: string, body = "assert.sameValue(1, 1);") => `/*---\n${yaml}\n---*/\n${body}`;
|
||||
|
||||
@@ -33,14 +33,13 @@ test("metadata generates the upstream variants without rewriting execution goals
|
||||
|
||||
test("unsupported execution requirements and assertion reflection remain exclusions", () => {
|
||||
for (const [head, body] of [
|
||||
["negative: {phase: parse, type: SyntaxError}", "invalid syntax"],
|
||||
["negative: {phase: parse, type: SyntaxError}", "assert.sameValue(1, 1);"],
|
||||
["negative: {phase: runtime, type: TypeError}", "throw new TypeError();"],
|
||||
["flags: [async]", "$DONE();"],
|
||||
["includes: [propertyHelper.js]", "verifyProperty({}, 'x', {});"],
|
||||
["description: global script", "assert.sameValue(this, globalThis);"],
|
||||
["description: reflection", "assert.sameValue(typeof assert, 'function');"],
|
||||
["description: mutation", "assert.sameValue = () => {};"],
|
||||
["description: unsupported helper", "assert.throws(TypeError, () => {});"],
|
||||
["description: unsupported helper", "assert.throws(CustomError, () => {});"],
|
||||
]) {
|
||||
const text = source(head!, body);
|
||||
expect(exclusion(text, metadata(text), "strict")).toBeTypeOf("string");
|
||||
@@ -48,6 +47,35 @@ test("unsupported execution requirements and assertion reflection remain exclusi
|
||||
const text = source("description: scalar", "// globalThis and this in comments are harmless\nassert.sameValue('this', 'this');");
|
||||
expect(exclusion(text, metadata(text), "strict")).toBeUndefined();
|
||||
expect(exclusion(text, metadata(text), "sloppy")).toBe("execution:sloppy");
|
||||
const asyncText = source("flags: [async]", "Promise.resolve().then(() => $DONE());");
|
||||
expect(exclusion(asyncText, metadata(asyncText), "strict")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("negative parse cases require the compiler's matching source diagnostic", () => {
|
||||
const text = source("negative: {phase: parse, type: SyntaxError}", "$DONOTEVALUATE();\nconst = ;");
|
||||
expect(exclusion(text, metadata(text), "strict")).toBeUndefined();
|
||||
expect(exclusion(text, metadata(text), "sloppy")).toBeUndefined();
|
||||
const offset = prepare(text).indexOf("const = ;") + "const ".length;
|
||||
const match = { status: "compile-refusal", diagnostics: [{
|
||||
code: "SC0001", message: "Variable declaration expected.", loc: { file: "/tmp/main.js", start: offset },
|
||||
}] };
|
||||
expect(matchesParseNegative(match, text)).toBe(true);
|
||||
expect(matchesParseNegative(match, text, "sloppy")).toBe(true);
|
||||
expect(matchesParseNegative({ status: "compile-refusal", diagnostics: [{
|
||||
code: "SC0001", message: "Cannot find name '$DONOTEVALUATE'.", loc: { file: "/tmp/main.js", start: 0 },
|
||||
}] }, text)).toBe(false);
|
||||
expect(matchesParseNegative({ status: "compiler-error", diagnostics: match.diagnostics }, text)).toBe(false);
|
||||
const strictOnly = source("negative: {phase: parse, type: SyntaxError}", "with ({}) {}");
|
||||
expect(exclusion(strictOnly, metadata(strictOnly), "sloppy")).toBe("negative-phase:parse");
|
||||
const generatedOnly = source("negative: {phase: parse, type: SyntaxError}", "function f() {");
|
||||
expect(exclusion(generatedOnly, metadata(generatedOnly), "strict")).toBe("negative-phase:parse");
|
||||
});
|
||||
|
||||
test("built-in error assertions are admitted without permitting constructor aliases", () => {
|
||||
const accepted = source("description: exact error", "assert.throws(TypeError, () => { throw new TypeError(); });");
|
||||
expect(exclusion(accepted, metadata(accepted), "strict")).toBeUndefined();
|
||||
const alias = source("description: custom error", "const Expected = TypeError; assert.throws(Expected, () => { throw new TypeError(); });");
|
||||
expect(exclusion(alias, metadata(alias), "strict")).toBe("harness:assert-surface");
|
||||
});
|
||||
|
||||
test("the compareArray include only admits the implemented assertion form", () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
||||
import { createContext, runInContext } from "node:vm";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { runSource } from "../test262/execute.js";
|
||||
import { exclusion, matchesExpectation, metadata, pin, vendorRoot, verifyVendor } from "../test262/support.mjs";
|
||||
import { exclusion, matchesExpectation, matchesParseNegative, metadata, pin, vendorRoot, verifyVendor } from "../test262/support.mjs";
|
||||
import { shardSelect, shardSuffix } from "./shard.js";
|
||||
|
||||
const sanitize = process.env.SCRIPTC_SAN === "1";
|
||||
@@ -20,6 +20,13 @@ test("Test262 regression inputs retain their pinned upstream bytes", () => {
|
||||
verifyVendor();
|
||||
});
|
||||
|
||||
test("negative parse cases require a matching compiler syntax diagnostic", async () => {
|
||||
const source = "/*---\nnegative: {phase: parse, type: SyntaxError}\n---*/\n$DONOTEVALUATE();\nconst = ;";
|
||||
expect(exclusion(source, metadata(source), "strict")).toBeUndefined();
|
||||
const result = await runSource(source, { sanitize });
|
||||
expect(matchesParseNegative(result, source), JSON.stringify(result, null, 2)).toBe(true);
|
||||
});
|
||||
|
||||
const profileCases = shardSelect<string>(pin.tests, (path) => `${path}#strict`);
|
||||
// The initial profile is small enough that a valid shard can own no cases.
|
||||
// Vitest rejects an empty describe block, so register this suite only when
|
||||
@@ -56,6 +63,11 @@ assert.notSameValue(1, "1");
|
||||
{ name: "array element mismatch", status: "fail", source: "assert.compareArray([1, 2], [1, 3]);" },
|
||||
{ name: "array signed zero mismatch", status: "fail", source: "assert.compareArray([0], [-0]);" },
|
||||
{ name: "assert requires true, not truthiness", status: "fail", source: "assert(1);" },
|
||||
{ name: "exact built-in exception", status: "pass", source: "assert.throws(TypeError, () => { throw new TypeError('x'); });" },
|
||||
{ name: "wrong built-in exception", status: "fail", source: "assert.throws(TypeError, () => { throw new RangeError('x'); });" },
|
||||
{ name: "subclass is not exact", status: "fail", source: "class E extends TypeError {} assert.throws(TypeError, () => { throw new E('x'); });" },
|
||||
{ name: "missing exception", status: "fail", source: "assert.throws(TypeError, () => {});" },
|
||||
{ name: "primitive exception", status: "fail", source: "assert.throws(TypeError, () => { throw 1; });" },
|
||||
];
|
||||
|
||||
describe(`Test262 host assertion contract${shardSuffix()}`, () => {
|
||||
@@ -96,4 +108,30 @@ describe(`Test262 host assertion contract${shardSuffix()}`, () => {
|
||||
const result = await runSource(source, { sanitize });
|
||||
expect(result, JSON.stringify(result)).toMatchObject({ status: "harness-refusal", reason: "reference-assertion" });
|
||||
});
|
||||
|
||||
test("non-Error objects do not masquerade as Error assertions", async () => {
|
||||
const source = "const value = JSON.parse('{\"constructor\":\"[builtin TypeError]\"}'); assert.throws(TypeError, () => { throw value; });";
|
||||
expect(() => runUpstream(source)).toThrow();
|
||||
const result = await runSource(source, { sanitize });
|
||||
expect(result, JSON.stringify(result)).toMatchObject({ status: "harness-refusal", reason: "non-error-assertion" });
|
||||
});
|
||||
});
|
||||
|
||||
const asyncControls = shardSelect([
|
||||
{ name: "promise completion", status: "pass", body: "Promise.resolve(1).then(value => { assert.sameValue(value, 1); $DONE(); });" },
|
||||
{ name: "completion error", status: "fail", body: "Promise.resolve().then(() => { $DONE('failure'); });" },
|
||||
{ name: "missing completion", status: "fail", body: "Promise.resolve().then(() => {});" },
|
||||
{ name: "duplicate completion", status: "fail", body: "$DONE(); $DONE();" },
|
||||
{ name: "completion marker spoof", status: "fail", body: "console.log('__scriptc_test262_complete__');" },
|
||||
], (item) => `async:${item.name}`);
|
||||
|
||||
if (asyncControls.length > 0) describe(`Test262 async host contract${shardSuffix()}`, () => {
|
||||
for (const control of asyncControls) {
|
||||
test(control.name, async () => {
|
||||
const source = `/*---\nflags: [async]\n---*/\n${control.body}`;
|
||||
expect(exclusion(source, metadata(source), "strict")).toBeUndefined();
|
||||
const result = await runSource(source, { sanitize, asyncTest: true });
|
||||
expect(result, JSON.stringify(result, null, 2)).toMatchObject({ status: control.status });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* the real CLI); stdout, stderr, and exit codes must agree. Mutations are
|
||||
* safe BECAUSE of the jail: every write (env add/rm, dns add/rm, alias
|
||||
* set/rm, project add) lands in the mock, whose endpoint trace pins the
|
||||
* request body byte-for-byte. the gateway e2e suite's three-legged pattern, pointed at a CLI
|
||||
* request body exactly. the gateway e2e suite's three-legged pattern, pointed at a CLI
|
||||
* whose network stack rides the island's native fetch. The compiled leg's
|
||||
* module-graph walls fell in sequence (node:https → smol-toml's dual
|
||||
* package → node:domain); the "reaches today" pin below records the
|
||||
@@ -49,12 +49,13 @@
|
||||
* attributes requests by bearer token, so concurrent lanes stay separable
|
||||
* and the per-command endpoint sequence is asserted per child.
|
||||
*
|
||||
* NORMALIZATIONS (both lanes, byte-exact otherwise): the lane cwd →
|
||||
* NORMALIZATIONS (both lanes, other content exact): the lane cwd →
|
||||
* "<CWD>", the banner's Node version → "(Node.js <V>)" (a build pin, not
|
||||
* semantics), spinner elapsed "[123ms]" → "[<T>]", and relative-time
|
||||
* tokens ("932d", "50s", "842d ago") → "<T>" — they are rendered against
|
||||
* wall-clock "now" from the mock's fixed timestamps, so a day boundary
|
||||
* mid-run would flip them. Absolute dates (inspect's created line, logs'
|
||||
* mid-run would flip them. Table alignment spaces also vary with age and
|
||||
* are collapsed while retaining cell boundaries. Absolute dates (inspect's created line, logs'
|
||||
* TIME column) are NOT normalized: children run under TZ=UTC, so the
|
||||
* mock's fixed epochs render machine-independently and any lane skew in
|
||||
* Date formatting is a real divergence. */
|
||||
@@ -245,6 +246,14 @@ function normalize(buf: Buffer, cwd: string): string {
|
||||
.replace(/False positive error reports may follow\n/g, "")
|
||||
.replace(/For details see https:\/\/github\.com\/google\/sanitizers\/issues\/189\n/g, "");
|
||||
}
|
||||
// Relative ages change table column widths as the fixed mock dates recede.
|
||||
// Preserve cell boundaries and contents while discarding alignment padding.
|
||||
text = text.split("\n").map((line) => {
|
||||
const unpadded = line.trimEnd();
|
||||
return /^ +\S.* {2,}\S/.test(unpadded)
|
||||
? ` ${unpadded.trimStart().replace(/ {2,}/g, " ")}`
|
||||
: unpadded;
|
||||
}).join("\n");
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @param {unknown} expectedErrorConstructor
|
||||
* @param {() => unknown} func
|
||||
* @returns {"pass" | "missing" | "primitive" | "wrong" | "invalid" | "unsupported"}
|
||||
*/
|
||||
export function throwsOutcome(expectedErrorConstructor, func) {
|
||||
if (typeof func !== "function") return "invalid";
|
||||
try {
|
||||
func();
|
||||
} catch (thrown) {
|
||||
const value = thrown;
|
||||
if (typeof thrown !== "object" || thrown === null) return "primitive";
|
||||
if (!(thrown instanceof Error)) return "unsupported";
|
||||
return value.constructor === expectedErrorConstructor ? "pass" : "wrong";
|
||||
}
|
||||
return "missing";
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { compileFailureStatus, completion, harnessSource, prepare } from "./support.mjs";
|
||||
import { assertThrowsSource, compileFailureStatus, completion, harnessSource, prepare } from "./support.mjs";
|
||||
|
||||
export interface Outcome {
|
||||
status: string;
|
||||
@@ -62,6 +63,7 @@ export function boundedRun(command: string, args: string[], timeoutMs: number):
|
||||
|
||||
export async function runSource(source: string, options: {
|
||||
backend?: "default" | "llvm" | "c";
|
||||
asyncTest?: boolean;
|
||||
sanitize?: boolean;
|
||||
compileTimeoutMs?: number;
|
||||
runtimeTimeoutMs?: number;
|
||||
@@ -76,8 +78,10 @@ export async function runSource(source: string, options: {
|
||||
const requestFile = join(workDir, "request.json");
|
||||
let phase = "compile";
|
||||
try {
|
||||
writeFileSync(entry, prepare(source));
|
||||
writeFileSync(join(workDir, "harness.ts"), harnessSource);
|
||||
const marker = `${completion}:${randomBytes(16).toString("hex")}`;
|
||||
writeFileSync(entry, prepare(source, options.asyncTest ?? false, marker));
|
||||
writeFileSync(join(workDir, "harness.ts"), harnessSource.replace(completion, marker));
|
||||
writeFileSync(join(workDir, "assert-throws.js"), assertThrowsSource);
|
||||
writeFileSync(requestFile, JSON.stringify({
|
||||
entry, binary, workDir, result: resultFile,
|
||||
backend: options.backend ?? "default", sanitize: options.sanitize ?? false,
|
||||
@@ -106,7 +110,10 @@ export async function runSource(source: string, options: {
|
||||
if (result.code === 86 && stderr === "SCRIPTC_TEST262_HARNESS: reference equality requires an identity-preserving adapter\n") {
|
||||
return finish({ ...common, status: "harness-refusal", phase, reason: "reference-assertion", stderr });
|
||||
}
|
||||
if (result.code === 0 && !result.overflow && stderr === "" && result.stdout === `${completion}\n`) {
|
||||
if (result.code === 86 && stderr === "SCRIPTC_TEST262_HARNESS: assert.throws needs an Error instance\n") {
|
||||
return finish({ ...common, status: "harness-refusal", phase, reason: "non-error-assertion", stderr });
|
||||
}
|
||||
if (result.code === 0 && !result.overflow && stderr === "" && result.stdout === `${marker}\n`) {
|
||||
return finish({ ...common, status: "pass" });
|
||||
}
|
||||
// Require the entire diagnostic, so a sanitizer report following a known
|
||||
|
||||
@@ -2,9 +2,21 @@
|
||||
// adapter avoids JS function expandos, which currently hit a static fence.
|
||||
// Keep the supported surface small and check it against the upstream helpers.
|
||||
import nodeAssert from "node:assert/strict";
|
||||
import { throwsOutcome } from "./assert-throws.js";
|
||||
|
||||
export class Test262Error extends Error {}
|
||||
|
||||
let asyncDone = false;
|
||||
|
||||
export function $DONE(error?: unknown): void {
|
||||
if (asyncDone || error) {
|
||||
console.error("SCRIPTC_TEST262_HARNESS: async completion failed");
|
||||
process.exit(87);
|
||||
}
|
||||
asyncDone = true;
|
||||
console.log("__scriptc_test262_complete__");
|
||||
}
|
||||
|
||||
function scalar(value: unknown): void {
|
||||
// Native-to-unknown conversion can copy references (notably arrays).
|
||||
// Refuse these assertions before comparing altered identities. Terminate
|
||||
@@ -40,6 +52,16 @@ export namespace assert {
|
||||
}
|
||||
}
|
||||
|
||||
export function throws(expectedErrorConstructor: unknown, func: () => unknown, message = "Expected exception"): void {
|
||||
const outcome = throwsOutcome(expectedErrorConstructor, func);
|
||||
if (outcome === "invalid") throw new Test262Error("assert.throws requires a function to run");
|
||||
if (outcome === "unsupported") {
|
||||
console.error("SCRIPTC_TEST262_HARNESS: assert.throws needs an Error instance");
|
||||
process.exit(86);
|
||||
}
|
||||
if (outcome !== "pass") throw new Test262Error(message);
|
||||
}
|
||||
|
||||
export function compareArray(actual: unknown, expected: unknown, message = "Expected matching array contents"): void {
|
||||
if (!Array.isArray(actual) || !Array.isArray(expected)) {
|
||||
scalar(actual);
|
||||
|
||||
+12
-8
@@ -4,7 +4,7 @@ import { parseArgs } from "node:util";
|
||||
import { shardSelect } from "../harness/shard.js";
|
||||
import { runSource, type Outcome } from "./execute.js";
|
||||
import {
|
||||
directory, exclusion, matchesExpectation, metadata, pin, sha256, snapshotDigest, summarize,
|
||||
assertThrowsSource, directory, exclusion, harnessSource, matchesExpectation, matchesParseNegative, metadata, pin, sha256, snapshotDigest, summarize,
|
||||
testPaths, variants, vendorRoot, verifyVendor,
|
||||
} from "./support.mjs";
|
||||
|
||||
@@ -29,8 +29,8 @@ async function main(): Promise<void> {
|
||||
--list list variants and exclusions without compiling
|
||||
--keep retain generated sources and native artifacts
|
||||
SCRIPTC_SAN=1 enables sanitizers; SCRIPTC_TEST_SHARD=i/n partitions variants.
|
||||
Only synchronous strict scripts in the documented adapted profile can pass.
|
||||
All other variants remain visible as exclusions; negative errors never count as passes.`);
|
||||
Adapted strict scripts and source-matched strict or sloppy syntax negatives can pass.
|
||||
Other variants remain visible as exclusions.`);
|
||||
return;
|
||||
}
|
||||
function positive(value: string | undefined, fallback: number, name: string): number {
|
||||
@@ -56,7 +56,8 @@ All other variants remain visible as exclusions; negative errors never count as
|
||||
const source = readFileSync(join(root, path), "utf8");
|
||||
const meta = metadata(source, path);
|
||||
return variants(meta).map((variant: string) => ({
|
||||
id: `${path}#${variant}`, path, variant, source, features: meta.features,
|
||||
id: `${path}#${variant}`, path, variant, source, features: meta.features, negative: meta.negative,
|
||||
asyncTest: meta.flags.includes("async"),
|
||||
exclusion: exclusion(source, meta, variant),
|
||||
}));
|
||||
});
|
||||
@@ -82,11 +83,14 @@ All other variants remain visible as exclusions; negative errors never count as
|
||||
for (;;) {
|
||||
const item = shard[next++];
|
||||
if (!item) return;
|
||||
const outcome = item.exclusion
|
||||
const raw = item.exclusion
|
||||
? { status: "excluded", reason: item.exclusion }
|
||||
: await runSource(item.source, {
|
||||
backend, sanitize: process.env.SCRIPTC_SAN === "1", compileTimeoutMs, runtimeTimeoutMs, keep: values.keep,
|
||||
backend, sanitize: process.env.SCRIPTC_SAN === "1", compileTimeoutMs, runtimeTimeoutMs, keep: values.keep, asyncTest: item.asyncTest,
|
||||
});
|
||||
const outcome = item.negative?.phase === "parse" && matchesParseNegative(raw, item.source, item.variant)
|
||||
? { status: "pass", phase: "compile" }
|
||||
: raw;
|
||||
const result = { id: item.id, path: item.path, variant: item.variant, features: item.features, ...outcome };
|
||||
if (journalPath) appendFileSync(journalPath, `${JSON.stringify(result)}\n`);
|
||||
results.push(result);
|
||||
@@ -99,8 +103,8 @@ All other variants remain visible as exclusions; negative errors never count as
|
||||
const report = {
|
||||
schema: "scriptc.test262.v1",
|
||||
revision: pin.commit, snapshotSha256: pin.snapshotSha256,
|
||||
profile: "static-strict-scalar-adapter-v1",
|
||||
harnessSha256: sha256(readFileSync(join(directory, "harness.ts"))),
|
||||
profile: "static-adapted-v2",
|
||||
harnessSha256: sha256(harnessSource + assertThrowsSource),
|
||||
dynamic: false, requestedBackend: backend, sanitize: process.env.SCRIPTC_SAN === "1",
|
||||
host: `${process.platform}-${process.arch}`, node: process.version,
|
||||
compilerVersion: JSON.parse(readFileSync(join(directory, "../../packages/compiler/package.json"), "utf8")).version,
|
||||
|
||||
@@ -10,6 +10,7 @@ export const vendorRoot = join(directory, "vendor");
|
||||
export const pin = JSON.parse(readFileSync(join(directory, "upstream.json"), "utf8"));
|
||||
export const completion = "__scriptc_test262_complete__";
|
||||
export const harnessSource = readFileSync(join(directory, "harness.ts"), "utf8");
|
||||
export const assertThrowsSource = readFileSync(join(directory, "assert-throws.js"), "utf8");
|
||||
export const expectations = JSON.parse(readFileSync(join(directory, "expectations.json"), "utf8"));
|
||||
export const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
||||
|
||||
@@ -87,15 +88,20 @@ export function variants(meta) {
|
||||
// conservative about observable global-script semantics and helper reflection.
|
||||
// Exclusions are runner limitations, never implementation support claims.
|
||||
export function exclusion(source, meta, variant) {
|
||||
if (meta.negative) {
|
||||
if (meta.negative.phase !== "parse" || meta.negative.type !== "SyntaxError" || !parseDiagnostics(source, variant).length) {
|
||||
return `negative-phase:${meta.negative.phase}`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (variant !== "strict") return `execution:${variant}`;
|
||||
if (meta.negative) return `negative-phase:${meta.negative.phase}`;
|
||||
if (meta.flags.includes("async")) return "execution:async";
|
||||
if (meta.flags.some((flag) => flag.startsWith("CanBlock"))) return "host:agents";
|
||||
const unsupportedIncludes = meta.includes.filter((name) => name !== "compareArray.js");
|
||||
if (unsupportedIncludes.length) return `harness-includes:${unsupportedIncludes.join(",")}`;
|
||||
const sf = ts.createSourceFile("test.js", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
|
||||
let reason;
|
||||
const forbidden = new Set(["$262", "$DONE", "$DONOTEVALUATE", "globalThis", "eval", "Function", "print", "process", "require", "arguments"]);
|
||||
if (meta.flags.includes("async")) forbidden.delete("$DONE");
|
||||
const visit = (node) => {
|
||||
if (reason) return;
|
||||
if (node.kind === ts.SyntaxKind.ThisKeyword || node.kind === ts.SyntaxKind.ImportKeyword || ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
||||
@@ -107,8 +113,13 @@ export function exclusion(source, meta, variant) {
|
||||
if (ts.isCallExpression(parent) && parent.expression === node) {
|
||||
// assert(value, message)
|
||||
} else if (ts.isPropertyAccessExpression(parent) && parent.expression === node &&
|
||||
["sameValue", "notSameValue", "compareArray"].includes(parent.name.text) &&
|
||||
ts.isCallExpression(parent.parent) && parent.parent.expression === parent) {
|
||||
ts.isCallExpression(parent.parent) && parent.parent.expression === parent &&
|
||||
(["sameValue", "notSameValue", "compareArray"].includes(parent.name.text) ||
|
||||
(parent.name.text === "throws" &&
|
||||
parent.parent.arguments[0] !== undefined &&
|
||||
ts.isIdentifier(parent.parent.arguments[0]) &&
|
||||
["Error", "TypeError", "RangeError", "SyntaxError"].includes(parent.parent.arguments[0].text)))
|
||||
) {
|
||||
// Supported assertion calls; aliases, mutations, and reflection stay out.
|
||||
} else reason = "harness:assert-surface";
|
||||
} else if (meta.includes.includes("compareArray.js") && ts.isIdentifier(node) && node.text === "compareArray" &&
|
||||
@@ -123,8 +134,36 @@ export function exclusion(source, meta, variant) {
|
||||
return reason;
|
||||
}
|
||||
|
||||
export function prepare(source) {
|
||||
return `"use strict";\nimport { assert, Test262Error } from "./harness.ts";\n${source}\n;console.log(${JSON.stringify(completion)});\n`;
|
||||
export function prepare(source, asyncTest = false, marker = completion) {
|
||||
const names = asyncTest ? "assert, Test262Error, $DONE" : "assert, Test262Error";
|
||||
const end = asyncTest ? "" : `;console.log(${JSON.stringify(marker)});\n`;
|
||||
return `"use strict";\nimport { ${names} } from "./harness.ts";\n${source}\n${end}`;
|
||||
}
|
||||
|
||||
function parseDiagnostics(source, variant) {
|
||||
if (variant !== "strict" && variant !== "sloppy") return [];
|
||||
const prepared = prepare(source);
|
||||
const sourceStart = prepared.indexOf(source);
|
||||
const sourceEnd = sourceStart + source.length;
|
||||
const file = ts.createSourceFile("main.js", prepared, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
|
||||
const raw = variant === "sloppy"
|
||||
? ts.createSourceFile("test.js", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS).parseDiagnostics
|
||||
: [];
|
||||
return file.parseDiagnostics.filter((diagnostic) =>
|
||||
diagnostic.start >= sourceStart && diagnostic.start < sourceEnd &&
|
||||
(variant === "strict" || raw.some((item) =>
|
||||
item.start === diagnostic.start - sourceStart &&
|
||||
ts.flattenDiagnosticMessageText(item.messageText, "\n") ===
|
||||
ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"))));
|
||||
}
|
||||
|
||||
export function matchesParseNegative(outcome, source, variant = "strict") {
|
||||
if (outcome.status !== "compile-refusal") return false;
|
||||
return parseDiagnostics(source, variant).some((parsed) => outcome.diagnostics?.some((reported) =>
|
||||
reported.code === "SC0001" &&
|
||||
reported.loc?.file?.endsWith("/main.js") &&
|
||||
reported.loc.start === parsed.start &&
|
||||
reported.message === ts.flattenDiagnosticMessageText(parsed.messageText, "\n")));
|
||||
}
|
||||
|
||||
export function summarize(results) {
|
||||
|
||||
Reference in New Issue
Block a user