mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-25 06:24:27 +08:00
Preserve inferred JavaScript behavior across static workflows
This commit is contained in:
@@ -299,11 +299,15 @@ export function streamTypedRefCommitAdapter(host: LlvmEmitterContext,
|
||||
` call void @scr_str_release(ptr %bag_key${index})`,
|
||||
);
|
||||
});
|
||||
if (symbols.size > 0) host.declare(`declare void @scr_dyn_key_delete_computed(ptr, ptr, i1 zeroext)`);
|
||||
if (symbols.size > 0) host.declare(meta.def.tracksOwnFields
|
||||
? `declare void @scr_dyn_symbol_key_placeholder(ptr, ptr)`
|
||||
: `declare void @scr_dyn_key_delete_computed(ptr, ptr, i1 zeroext)`);
|
||||
[...symbols.values()].forEach((globalId, index) => lines.push(
|
||||
` %bag_symraw${index} = load ptr, ptr @${mangleGlobal(globalId)}`,
|
||||
` %bag_sym${index} = call ptr @${host.dyn.toDynHelper(SYMBOL_T)}(ptr %bag_symraw${index})`,
|
||||
` call void @scr_dyn_key_delete_computed(ptr %bag, ptr %bag_sym${index}, i1 zeroext false)`,
|
||||
meta.def.tracksOwnFields
|
||||
? ` call void @scr_dyn_symbol_key_placeholder(ptr %bag, ptr %bag_sym${index})`
|
||||
: ` call void @scr_dyn_key_delete_computed(ptr %bag, ptr %bag_sym${index}, i1 zeroext false)`,
|
||||
` call void @scr_dyn_release_v(ptr %bag_sym${index})`,
|
||||
));
|
||||
const { index } = classFieldIndex(meta, DYN_CLASS_PROPERTIES);
|
||||
@@ -642,10 +646,15 @@ export function streamTypedRefMaterializeAdapter(host: LlvmEmitterContext,
|
||||
let fieldValue = B.tmp();
|
||||
B.line(`${fieldPtr} = getelementptr inbounds %${classStructSym(t.className)}, ptr %p, i64 0, i32 ${index}`);
|
||||
B.line(`${fieldValue} = load ${llFieldType(field.type)}, ptr ${fieldPtr}`);
|
||||
const afterSymbol = field.type.kind === "symbol" ? B.newLabel("symbol.after") : null;
|
||||
B.line(`${rawKey} = load ptr, ptr @${mangleGlobal(symbol.globalId)}`);
|
||||
const key = host.streamTypedRefBoxValue(B, SYMBOL_T, rawKey);
|
||||
const afterSymbol = meta.def.tracksOwnFields || field.type.kind === "symbol" ? B.newLabel("symbol.after") : null;
|
||||
if (afterSymbol) {
|
||||
const initialized = B.tmp(), present = B.newLabel("symbol.present");
|
||||
B.line(`${initialized} = icmp ne ptr ${fieldValue}, null`);
|
||||
if (meta.def.tracksOwnFields) {
|
||||
host.declare(`declare zeroext i1 @scr_dyn_has_own_computed(ptr, ptr)`);
|
||||
B.line(`${initialized} = call zeroext i1 @scr_dyn_has_own_computed(ptr ${out}, ptr ${key})`);
|
||||
} else B.line(`${initialized} = icmp ne ptr ${fieldValue}, null`);
|
||||
B.condBr(initialized, present, afterSymbol);
|
||||
B.startBlock(present);
|
||||
}
|
||||
@@ -654,18 +663,16 @@ export function streamTypedRefMaterializeAdapter(host: LlvmEmitterContext,
|
||||
B.line(`${boolValue} = trunc i8 ${fieldValue} to i1`);
|
||||
fieldValue = boolValue;
|
||||
}
|
||||
B.line(`${rawKey} = load ptr, ptr @${mangleGlobal(symbol.globalId)}`);
|
||||
const key = host.streamTypedRefBoxValue(B, SYMBOL_T, rawKey);
|
||||
const value = host.streamTypedRefBoxValue(B, field.type, fieldValue);
|
||||
host.declare(`declare void @scr_dyn_symbol_key_set(ptr, ptr, ptr)`);
|
||||
host.declare(`declare void @scr_dyn_release_v(ptr)`);
|
||||
B.line(`call void @scr_dyn_symbol_key_set(ptr ${out}, ptr ${key}, ptr ${value})`);
|
||||
B.line(`call void @scr_dyn_release_v(ptr ${key})`);
|
||||
B.line(`call void @scr_dyn_release_v(ptr ${value})`);
|
||||
if (afterSymbol) {
|
||||
B.terminate(`br label %${afterSymbol}`);
|
||||
B.startBlock(afterSymbol);
|
||||
}
|
||||
B.line(`call void @scr_dyn_release_v(ptr ${key})`);
|
||||
}
|
||||
if (!meta.def.tracksOwnFields && meta.def.fields.some((field) => field.name === DYN_CLASS_PROPERTIES)) {
|
||||
host.declare(`declare ptr @scr_dyn_copy_property_descriptors(ptr, ptr)`);
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as ts from "./ts7/adapter.js";
|
||||
export function functionCanFallThrough(declaration: ts.Node): boolean {
|
||||
if (!ts.isFunctionLike(declaration) || declaration.asteriskToken ||
|
||||
declaration.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) return false;
|
||||
const body = "body" in declaration ? declaration.body as ts.Block | ts.Expression | undefined : undefined;
|
||||
const body = declaration.body as ts.Block | ts.Expression | undefined;
|
||||
if (!body || !ts.isBlock(body)) return false;
|
||||
const completes = (statement: ts.Statement): boolean => {
|
||||
if (ts.isReturnStatement(statement) || ts.isThrowStatement(statement)) return false;
|
||||
@@ -22,3 +22,18 @@ export function functionCanFallThrough(declaration: ts.Node): boolean {
|
||||
const sequence = (statements: readonly ts.Statement[]): boolean => statements.every(completes);
|
||||
return sequence(body.statements);
|
||||
}
|
||||
|
||||
/** A documented JavaScript result must also represent explicit empty returns. */
|
||||
export function functionCanReturnUndefined(declaration: ts.Node): boolean {
|
||||
if (functionCanFallThrough(declaration)) return true;
|
||||
if (!ts.isFunctionLike(declaration) || declaration.asteriskToken ||
|
||||
declaration.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) return false;
|
||||
const body = declaration.body;
|
||||
if (!body) return false;
|
||||
let emptyReturn = false;
|
||||
ts.walkPreorder(body, (node) => {
|
||||
if (node !== body && ts.isFunctionLike(node)) return "skip";
|
||||
if (ts.isReturnStatement(node) && (!node.expression || ts.isVoidExpression(node.expression))) emptyReturn = true;
|
||||
});
|
||||
return emptyReturn;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DYN, STRING, type IrExpr, type SrcLoc } from "../../ir/ir.js";
|
||||
import { BOOL, DYN, STRING, type IrExpr, type SrcLoc } from "../../ir/ir.js";
|
||||
import { varRef } from "../../ir/build.js";
|
||||
import { newFnCtx, type Lowerer } from "./lowerer.js";
|
||||
import { lowerCheckedArrayFrom } from "./lower-containers.js";
|
||||
@@ -11,7 +11,13 @@ export function checkedIterableSpread(lowerer: Lowerer, source: IrExpr, spelling
|
||||
lowerer.fnStack.push(context);
|
||||
try {
|
||||
const params = [{ localId: "source", name: "source", type: DYN }, { localId: "spelling", name: "spelling", type: STRING }];
|
||||
const result = lowerCheckedArrayFrom(lowerer, varRef("source", DYN, loc), loc, undefined, undefined, false, varRef("spelling", STRING, loc));
|
||||
const sourceValue = varRef("source", DYN, loc);
|
||||
const text = (value: string): IrExpr => ({ kind: "strLit", value, type: STRING, loc });
|
||||
const nullishMessage = (value: string): IrExpr => ({ kind: "strConcat", left: varRef("spelling", STRING, loc), right: text(` is not iterable (cannot read property ${value})`), type: STRING, loc });
|
||||
const message: IrExpr = { kind: "ternary", cond: { kind: "dynTest", test: "null", value: sourceValue, type: BOOL, loc }, then: nullishMessage("null"),
|
||||
else_: { kind: "ternary", cond: { kind: "dynTest", test: "undefined", value: sourceValue, type: BOOL, loc }, then: nullishMessage("undefined"),
|
||||
else_: text("Spread syntax requires ...iterable[Symbol.iterator] to be a function"), type: STRING, loc }, type: STRING, loc };
|
||||
const result = lowerCheckedArrayFrom(lowerer, sourceValue, loc, undefined, undefined, false, message);
|
||||
lowerer.liftedFns.push({ name, params, returnType: DYN,
|
||||
locals: [...params.map((param) => ({ id: param.localId, name: param.name, type: param.type, mutable: false })), ...context.locals],
|
||||
body: [{ kind: "return", value: result, loc }], loc });
|
||||
|
||||
@@ -16,12 +16,25 @@ import { SYMBOL_T } from "../../ir/ir.js";
|
||||
import { isClassCallback } from "./class-callbacks.js";
|
||||
import { refreshDescriptorGuards } from "./class-descriptors.js";
|
||||
import { ClassConstructionDispatch } from "./class-construction.js";
|
||||
import { emitterRooted } from "./lower-event-emitter.js";
|
||||
|
||||
const STREAM_METHODS = new Set(["on", "addListener", "once", "prependListener", "prependOnceListener", "off", "removeListener", "read", "pause", "resume", "isPaused", "destroy"]);
|
||||
const EMITTER_METHODS = new Set(["emit", "on", "addListener", "once", "prependListener", "prependOnceListener", "off", "removeListener"]);
|
||||
const STREAM_BOOL_PROPERTIES = new Set(["readable", "readableEnded", "writable", "writableEnded", "writableFinished", "writableNeedDrain", "destroyed", "closed", "readableObjectMode", "writableObjectMode", "allowHalfOpen"]);
|
||||
const STREAM_NUM_PROPERTIES = new Set(["readableLength", "readableHighWaterMark", "writableLength", "writableHighWaterMark", "writableCorked"]);
|
||||
const streamProperty = (name: string): boolean => STREAM_BOOL_PROPERTIES.has(name) || STREAM_NUM_PROPERTIES.has(name);
|
||||
|
||||
function symbolMemberKey(lowerer: Lowerer, info: ClassInfo, name: string, loc: SrcLoc): IrExpr | null {
|
||||
if (name.startsWith("sym:")) return lowerer.coerceToExpected({ kind: "libCall", fn: "sym.wellKnown",
|
||||
args: [{ kind: "strLit", value: name.slice(4), type: STRING, loc }], type: SYMBOL_T, loc }, DYN);
|
||||
for (const [symbol, member] of [...(info.symbolFields ?? []), ...(info.symbolMethods ?? [])]) {
|
||||
if (member !== name) continue;
|
||||
const global = lowerer.globalsBySymbol.get(symbol);
|
||||
if (global) return lowerer.coerceToExpected(varRef(global.id, global.type, loc), DYN);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
type Invoke = Extract<IrExpr, { kind: "dynInvoke" }>;
|
||||
interface Dispatch {
|
||||
source: Invoke;
|
||||
@@ -107,13 +120,15 @@ export function trackClassFieldCreation(lowerer: Lowerer, functions: IrFunction[
|
||||
if (![...lowerer.classes.values()].some((info) => info.def.tracksOwnFields)) return;
|
||||
const loc = functions[0]!.loc;
|
||||
const name = "%dyn.class.fieldCreated";
|
||||
const value = varRef("value", DYN, loc), key = varRef("key", STRING, loc);
|
||||
const value = varRef("value", DYN, loc), key = varRef("key", DYN, loc);
|
||||
const bag = varRef("bag", DYN, loc);
|
||||
const created = (receiver: IrExpr, field: string, loc: SrcLoc): IrStmt => ({ kind: "exprStmt", expr: {
|
||||
kind: "call", callee: name, args: [lowerer.coerceToExpected(receiver, DYN), { kind: "strLit", value: field, type: STRING, loc }], type: VOID, loc,
|
||||
const created = (receiver: IrExpr, className: string, field: string, loc: SrcLoc): IrStmt => ({ kind: "exprStmt", expr: {
|
||||
kind: "call", callee: name, args: [lowerer.coerceToExpected(receiver, DYN), symbolMemberKey(lowerer, lowerer.classes.get(className)!, field, loc) ??
|
||||
lowerer.coerceToExpected({ kind: "strLit", value: field, type: STRING, loc }, DYN)], type: VOID, loc,
|
||||
}, loc });
|
||||
const tracked = (className: string, field: string): boolean =>
|
||||
!!lowerer.classes.get(className)?.def.tracksOwnFields && isClassOwnEnumerableFieldName(field);
|
||||
!!lowerer.classes.get(className)?.def.tracksOwnFields && (isClassOwnEnumerableFieldName(field) ||
|
||||
symbolMemberKey(lowerer, lowerer.classes.get(className)!, field, loc) !== null);
|
||||
for (const fn of functions) {
|
||||
const instrumented = new WeakSet<object>();
|
||||
const local = (type: IrType, loc: SrcLoc): Extract<IrExpr, { kind: "varRef" }> => {
|
||||
@@ -125,31 +140,31 @@ export function trackClassFieldCreation(lowerer: Lowerer, functions: IrFunction[
|
||||
stmt: (statement) => {
|
||||
if (statement.kind !== "fieldSet" || instrumented.has(statement) || !tracked(statement.className, statement.field)) return statement;
|
||||
const receiver = local(statement.obj.type, statement.loc);
|
||||
const store = { ...statement, obj: receiver };
|
||||
const store: Extract<IrStmt, { kind: "fieldSet" }> = { ...statement, obj: receiver };
|
||||
instrumented.add(store);
|
||||
return { kind: "block", body: [
|
||||
{ kind: "varDecl", localId: receiver.localId, init: statement.obj, loc: statement.loc },
|
||||
store, created(receiver, statement.field, statement.loc),
|
||||
store, created(receiver, statement.className, statement.field, statement.loc),
|
||||
], loc: statement.loc };
|
||||
},
|
||||
expr: (expression) => {
|
||||
if (expression.kind !== "fieldIncDec" || instrumented.has(expression) || !tracked(expression.className, expression.field)) return expression;
|
||||
const receiver = local(expression.obj.type, expression.loc), result = local(expression.type, expression.loc);
|
||||
const update = { ...expression, obj: receiver };
|
||||
const update: Extract<IrExpr, { kind: "fieldIncDec" }> = { ...expression, obj: receiver };
|
||||
instrumented.add(update);
|
||||
return { kind: "seqExpr", stmts: [
|
||||
{ kind: "varDecl", localId: receiver.localId, init: expression.obj, loc: expression.loc },
|
||||
{ kind: "varDecl", localId: result.localId, init: update, loc: expression.loc },
|
||||
created(receiver, expression.field, expression.loc),
|
||||
created(receiver, expression.className, expression.field, expression.loc),
|
||||
], result, type: expression.type, loc: expression.loc };
|
||||
},
|
||||
});
|
||||
}
|
||||
functions.push({ name, params: [{ localId: "value", name: "value", type: DYN }, { localId: "key", name: "key", type: STRING }], returnType: VOID,
|
||||
locals: [{ id: "value", name: "value", type: DYN, mutable: false }, { id: "key", name: "key", type: STRING, mutable: false }, { id: "bag", name: "bag", type: DYN, mutable: false }],
|
||||
functions.push({ name, params: [{ localId: "value", name: "value", type: DYN }, { localId: "key", name: "key", type: DYN }], returnType: VOID,
|
||||
locals: [{ id: "value", name: "value", type: DYN, mutable: false }, { id: "key", name: "key", type: DYN, mutable: false }, { id: "bag", name: "bag", type: DYN, mutable: false }],
|
||||
body: [
|
||||
{ kind: "varDecl", localId: "bag", init: { kind: "call", callee: classPropertiesHelper(lowerer, loc).name, args: [value], type: DYN, loc }, loc },
|
||||
{ kind: "if", cond: { kind: "unary", op: "!", operand: { kind: "libCall", fn: "dyn.hasOwn", args: [bag, key], type: BOOL, loc }, type: BOOL, loc }, then: [
|
||||
{ kind: "if", cond: { kind: "unary", op: "!", operand: { kind: "libCall", fn: "dyn.hasOwnComputed", args: [bag, key], type: BOOL, loc }, type: BOOL, loc }, then: [
|
||||
{ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.defineProperty", args: [bag, lowerer.coerceToExpected(key, DYN), { kind: "dynObjLit", fields: [
|
||||
{ key: { kind: "strLit", value: "value", type: STRING, loc }, value: dynUndefinedExpr(loc) },
|
||||
...["writable", "enumerable", "configurable"].map((key) => ({ key: { kind: "strLit" as const, value: key, type: STRING, loc }, value: lowerer.coerceToExpected({ kind: "boolLit", value: true, type: BOOL, loc }, DYN) })),
|
||||
@@ -445,7 +460,8 @@ export class ClassDynamicDispatch {
|
||||
const found = byMethod.get(method);
|
||||
if (found) return found;
|
||||
const matching = this.propertyReceivers(lowerer, true).flatMap(({ info }) => {
|
||||
if (!info || info.fields.has(method) || info.builtinEmitter || info.builtinError) return [];
|
||||
if (!info || info.fields.has(method) || info.builtinError) return [];
|
||||
if (emitterRooted(lowerer, info) && EMITTER_METHODS.has(method)) return [info];
|
||||
if (info.builtinStream) return STREAM_METHODS.has(method) ? [info] : [];
|
||||
return findMethodOn(lowerer, info, method) || findGenericMethodOn(lowerer, info, method) ? [info] : [];
|
||||
});
|
||||
@@ -925,8 +941,10 @@ export class ClassDynamicDispatch {
|
||||
if (info.fields.has(name) || dispatch.probe === "in" && !prototypeMethod) {
|
||||
if (info.fields.has(name) && info.def.tracksOwnFields) {
|
||||
const bag: IrExpr = { kind: "call", callee: this.propertyBag!.name, args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
||||
return [{ kind: "return", value: { kind: "libCall", fn: dispatch.probe === "in" ? "dyn.hasKey" : dispatch.probe === "own" ? "dyn.hasOwn" : "dyn.propertyIsEnumerable",
|
||||
args: [bag, { kind: "strLit", value: name, type: STRING, loc }], type: BOOL, loc }, loc }];
|
||||
const symbol = symbolMemberKey(lowerer, info, name, loc);
|
||||
const fn = dispatch.probe === "in" ? "dyn.hasKey" : dispatch.probe === "own" ? "dyn.hasOwn" : "dyn.propertyIsEnumerable";
|
||||
return [{ kind: "return", value: { kind: "libCall", fn: symbol ? `${fn}Computed` : fn,
|
||||
args: [bag, symbol ?? { kind: "strLit", value: name, type: STRING, loc }], type: BOOL, loc }, loc }];
|
||||
}
|
||||
return [{ kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc }];
|
||||
}
|
||||
@@ -1226,8 +1244,8 @@ export class ClassDynamicDispatch {
|
||||
const seen = new Set<string>();
|
||||
for (const name of this.boxed) {
|
||||
const capsule = lowerer.classes.get(name);
|
||||
if (!capsule || capsule.builtinEmitter || capsule.builtinStream && !streams || capsule.builtinError) continue;
|
||||
const canonical = normalize && !capsule.builtinStream;
|
||||
if (!capsule || (capsule.builtinEmitter || capsule.builtinStream) && !streams || capsule.builtinError) continue;
|
||||
const canonical = normalize && !capsule.builtinStream && !capsule.builtinEmitter;
|
||||
const visit = (info: ClassInfo): void => {
|
||||
if ((!canonical || !seen.has(info.def.name)) && (info === capsule || lowerer.classCanBeConstructed(info))) {
|
||||
seen.add(info.def.name);
|
||||
@@ -1347,14 +1365,15 @@ export class ClassDynamicDispatch {
|
||||
(candidate.methods.has(name) || candidate.genericMethods?.has(name)));
|
||||
overrides.sort((a, b) => lowerer.isSubclassOf(a.def.name, b.def.name) ? -1 : lowerer.isSubclassOf(b.def.name, a.def.name) ? 1 : 0);
|
||||
const body: IrStmt[] = [];
|
||||
if (isClassOwnEnumerableFieldName(name) && !name.startsWith("sym:")) {
|
||||
const symbol = symbolMemberKey(lowerer, info, name, loc);
|
||||
if (symbol || isClassOwnEnumerableFieldName(name) && !name.startsWith("sym:")) {
|
||||
const prototypeMethod = lowerer.prototypeMethodAccesses.has(name);
|
||||
if (prototypeMethod) classPrototypeData(lowerer, info, loc, receiver);
|
||||
const bag: IrExpr = { kind: "call", callee: this.propertyBag!.name,
|
||||
args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
||||
const key: IrExpr = { kind: "strLit", value: name, type: STRING, loc };
|
||||
body.push({ kind: "if", cond: { kind: "libCall", fn: "dyn.hasKey", args: [bag, key], type: BOOL, loc },
|
||||
then: [{ kind: "return", value: { kind: "libCall", fn: "dyn.bagGet", args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }, loc }], else_: null, loc });
|
||||
const key: IrExpr = symbol ?? { kind: "strLit", value: name, type: STRING, loc };
|
||||
body.push({ kind: "if", cond: { kind: "libCall", fn: symbol ? "dyn.hasOwnComputed" : "dyn.hasKey", args: [bag, key], type: BOOL, loc },
|
||||
then: [{ kind: "return", value: { kind: "libCall", fn: symbol ? "dyn.reflectGet" : "dyn.bagGet", args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }, loc }], else_: null, loc });
|
||||
if (prototypeMethod) {
|
||||
body.push({ kind: "return", value: dynUndefinedExpr(loc), loc });
|
||||
return body;
|
||||
@@ -1397,9 +1416,10 @@ export class ClassDynamicDispatch {
|
||||
if (field && info.def.tracksOwnFields) {
|
||||
const instance = lowerer.coerceToExpected(receiver, DYN);
|
||||
const bag: IrExpr = { kind: "call", callee: this.propertyBag!.name, args: [instance], type: DYN, loc };
|
||||
const key: IrExpr = { kind: "strLit", value: name, type: STRING, loc };
|
||||
return [{ kind: "return", value: { kind: "ternary", cond: { kind: "libCall", fn: "dyn.hasOwn", args: [bag, key], type: BOOL, loc },
|
||||
then: boxed, else_: { kind: "libCall", fn: "dyn.bagGet", args: [bag, key, instance], type: DYN, loc }, type: DYN, loc }, loc }];
|
||||
const symbol = symbolMemberKey(lowerer, info, name, loc);
|
||||
const key: IrExpr = symbol ?? { kind: "strLit", value: name, type: STRING, loc };
|
||||
return [{ kind: "return", value: { kind: "ternary", cond: { kind: "libCall", fn: symbol ? "dyn.hasOwnComputed" : "dyn.hasOwn", args: [bag, key], type: BOOL, loc },
|
||||
then: boxed, else_: { kind: "libCall", fn: symbol ? "dyn.reflectGet" : "dyn.bagGet", args: [bag, key, instance], type: DYN, loc }, type: DYN, loc }, loc }];
|
||||
}
|
||||
return [{ kind: "return", value: boxed, loc }];
|
||||
}
|
||||
@@ -1430,16 +1450,21 @@ export class ClassDynamicDispatch {
|
||||
private methodBody(lowerer: Lowerer, dispatch: Dispatch, info: ClassInfo, receiver: IrExpr): IrStmt[] {
|
||||
const { method, loc } = dispatch.source;
|
||||
const fence = (): IrStmt[] => [{ kind: "runtimeFence", code: "SC2020", message: `calling '${method}' on this native class through an untyped value is not supported yet`, loc }];
|
||||
if (info.builtinStream) {
|
||||
if (info.builtinStream || emitterRooted(lowerer, info) && EMITTER_METHODS.has(method) && !findMethodOn(lowerer, info, method)) {
|
||||
const incoming = dispatch.source.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc));
|
||||
const registering = ["on", "addListener", "once", "prependListener", "prependOnceListener"].includes(method);
|
||||
const removing = method === "off" || method === "removeListener";
|
||||
const ret = (value: IrExpr): IrStmt[] => [{ kind: "return", value: lowerer.coerceToExpected(value, DYN), loc }];
|
||||
if (method === "emit") {
|
||||
if (!incoming.length) return fence();
|
||||
return ret({ kind: "libCall", fn: "emitter.emitFlex", args: [receiver,
|
||||
lowerer.coerceToExpected(incoming[0]!, STRING), ...incoming.slice(1)], type: BOOL, loc });
|
||||
}
|
||||
if (registering || removing) {
|
||||
if (incoming.length < 2) return fence();
|
||||
const event = lowerer.coerceToExpected(incoming[0]!, STRING);
|
||||
const cb = incoming[1]!;
|
||||
return [{ kind: "exprStmt", expr: { kind: "libCall", fn: "emitter.checkListener", args: [cb], type: VOID, loc }, loc }, ...ret({ kind: "libCall", fn: registering ? "stream.onDyn" : "emitter.offDyn", args: [receiver, event, cb, ...(registering ? [
|
||||
return [{ kind: "exprStmt", expr: { kind: "libCall", fn: "emitter.checkListener", args: [cb], type: VOID, loc }, loc }, ...ret({ kind: "libCall", fn: registering ? info.builtinStream ? "stream.onDyn" : "emitter.onFlex" : "emitter.offDyn", args: [receiver, event, cb, ...(registering ? [
|
||||
{ kind: "boolLit" as const, value: method === "once" || method === "prependOnceListener", type: BOOL, loc },
|
||||
{ kind: "boolLit" as const, value: method.startsWith("prepend"), type: BOOL, loc },
|
||||
] : [])], type: receiver.type, loc })];
|
||||
|
||||
@@ -26,6 +26,7 @@ export function classStaticDataFor(lowerer: Lowerer, info: ClassInfo, name: stri
|
||||
}
|
||||
|
||||
export function classStaticData(lowerer: Lowerer, info: ClassInfo, loc: SrcLoc): IrExpr | null {
|
||||
if (info.callableBase) return null;
|
||||
if (info.localClass || info.runtimeStatics || info.mixinInstance || info.generic || info.genericInstance || info.classDecorators || info.def.runtime || info.builtinEmitter || info.builtinStream || info.builtinError) return null;
|
||||
if (info.staticDataHelper === undefined) {
|
||||
const base = info.base && !info.base.builtinError ? classStaticData(lowerer, info.base, loc) : null;
|
||||
|
||||
@@ -134,6 +134,7 @@ export class DeferredModuleInitializers {
|
||||
if (!fn) continue;
|
||||
everyStmtList(fn.body, { stmt: () => true, expr: (expr) => {
|
||||
demandType(expr.type);
|
||||
if (expr.kind === "varRef" && this.pending.has(expr.localId)) demanded.add(expr.localId);
|
||||
if (expr.kind === "call") {
|
||||
const owner = classHelpers.get(expr.callee);
|
||||
if (owner) {
|
||||
@@ -188,8 +189,11 @@ function registryInitializer(lowerer: Lowerer, expression: ts.Expression, statem
|
||||
return ts.isVariableDeclaration(declaration) && initialized(declaration) && !!declaration.initializer &&
|
||||
(ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer));
|
||||
};
|
||||
const value = (node: ts.Expression): boolean => {
|
||||
if (ts.isParenthesizedExpression(node)) return value(node.expression);
|
||||
const value = (node: ts.Expression, depth = 0): boolean => {
|
||||
// Let ordinary expression lowering report its named depth limit.
|
||||
// Registry discovery must not overflow before that check runs.
|
||||
if (depth > 200) return false;
|
||||
if (ts.isParenthesizedExpression(node)) return value(node.expression, depth + 1);
|
||||
if (ts.isPrefixUnaryExpression(node) && (node.operator === ts.SyntaxKind.MinusToken || node.operator === ts.SyntaxKind.PlusToken) && ts.isNumericLiteral(node.operand)) return true;
|
||||
if (ts.isNumericLiteral(node) || ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) ||
|
||||
node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword || node.kind === ts.SyntaxKind.NullKeyword) return true;
|
||||
@@ -201,7 +205,7 @@ function registryInitializer(lowerer: Lowerer, expression: ts.Expression, statem
|
||||
const trivia = node.getSourceFile().text.slice(node.pos, node.getStart());
|
||||
if (!/[@#]__PURE__/.test(trivia)) return false;
|
||||
if (!callable(node.expression)) return false;
|
||||
return (node.arguments ?? []).every(value);
|
||||
return (node.arguments ?? []).every((argument) => value(argument, depth + 1));
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(node) && ts.isPropertyAccessExpression(node.expression) &&
|
||||
node.expression.name.text === "prototype") {
|
||||
@@ -217,14 +221,14 @@ function registryInitializer(lowerer: Lowerer, expression: ts.Expression, statem
|
||||
const owner = ts.isIdentifier(node.expression) ? lowerer.resolveValueSymbol(node.expression) : undefined;
|
||||
return !!declaration && ts.isPropertyAssignment(declaration) &&
|
||||
!ts.isComputedPropertyName(declaration.name) && propertyName(declaration.name) !== "__proto__" &&
|
||||
!!owner && registryOwnerIsData(lowerer, owner, statement) && value(node.expression);
|
||||
!!owner && registryOwnerIsData(lowerer, owner, statement) && value(node.expression, depth + 1);
|
||||
}
|
||||
if (ts.isArrayLiteralExpression(node)) return node.elements.every((element) => !ts.isSpreadElement(element) && value(element));
|
||||
if (ts.isArrayLiteralExpression(node)) return node.elements.every((element) => !ts.isSpreadElement(element) && value(element, depth + 1));
|
||||
if (ts.isObjectLiteralExpression(node)) return node.properties.every((property) => {
|
||||
if (ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) return !!property.name && !ts.isComputedPropertyName(property.name);
|
||||
if (ts.isShorthandPropertyAssignment(property)) return !property.objectAssignmentInitializer && ts.isIdentifier(property.name) && value(property.name);
|
||||
if (ts.isShorthandPropertyAssignment(property)) return !property.objectAssignmentInitializer && ts.isIdentifier(property.name) && value(property.name, depth + 1);
|
||||
return ts.isPropertyAssignment(property) && !ts.isComputedPropertyName(property.name) &&
|
||||
(propertyName(property.name) !== "__proto__" || property.initializer.kind === ts.SyntaxKind.NullKeyword) && value(property.initializer);
|
||||
(propertyName(property.name) !== "__proto__" || property.initializer.kind === ts.SyntaxKind.NullKeyword) && value(property.initializer, depth + 1);
|
||||
});
|
||||
if (!ts.isIdentifier(node)) return false;
|
||||
if (node.text === "undefined" && lowerer.isStdlibGlobal(node, "undefined")) return true;
|
||||
|
||||
@@ -260,6 +260,7 @@ export function lowerDynObjectLiteral(
|
||||
key = { kind: "strLit", value: folded, type: STRING, loc: locOf(name) };
|
||||
} else {
|
||||
let k = lowerer.lowerExpr(name.expression);
|
||||
if (k.type.kind === "union" && lowerer.dynConvertible(k.type)) k = lowerer.coerceToExpected(k, DYN);
|
||||
if (k.type.kind === "symbol") k = lowerer.coerceToExpected(k, DYN);
|
||||
else if (k.type.kind === "dyn") k = { kind: "libCall", fn: "dyn.propertyKey", args: [k], type: DYN, loc: locOf(name) };
|
||||
else if (k.type.kind === "f64" || k.type.kind === "bool") {
|
||||
@@ -1168,6 +1169,7 @@ export function lowerObjectLiteral(lowerer: Lowerer, expr: ts.ObjectLiteralExpre
|
||||
// Number/boolean/unknown keys stringify (ToPropertyKey).
|
||||
if (ts.isPropertyAssignment(prop) && ts.isComputedPropertyName(prop.name)) {
|
||||
let k = lowerer.lowerExpr(prop.name.expression);
|
||||
if (k.type.kind === "union" && lowerer.dynConvertible(k.type)) k = lowerer.coerceToExpected(k, DYN);
|
||||
if (k.type.kind === "f64" || k.type.kind === "bool" || k.type.kind === "dyn") {
|
||||
k = { kind: "toString", operand: k, type: STRING, loc: locOf(prop.name) };
|
||||
}
|
||||
|
||||
@@ -129,19 +129,19 @@ export function lowerOptionalChain(lowerer: Lowerer, expr: ts.CallExpression | t
|
||||
// The node CARRYING the ?. token and the receiver expression it guards.
|
||||
let dotNode: ts.Node;
|
||||
let recvNode: ts.Expression;
|
||||
if (ts.isCallExpression(expr) && expr.questionDotToken) {
|
||||
if (ts.isCallExpression(expr) && expr.questionDotToken && !lowerer.chainHandled.has(expr)) {
|
||||
dotNode = expr; // f?.()
|
||||
recvNode = expr.expression;
|
||||
} else if (
|
||||
ts.isCallExpression(expr) &&
|
||||
(ts.isPropertyAccessExpression(expr.expression) || ts.isElementAccessExpression(expr.expression)) &&
|
||||
expr.expression.questionDotToken
|
||||
expr.expression.questionDotToken && !lowerer.chainHandled.has(expr.expression)
|
||||
) {
|
||||
dotNode = expr.expression; // a?.m()
|
||||
recvNode = expr.expression.expression;
|
||||
} else if (
|
||||
(ts.isPropertyAccessExpression(expr) || ts.isElementAccessExpression(expr)) &&
|
||||
expr.questionDotToken
|
||||
expr.questionDotToken && !lowerer.chainHandled.has(expr)
|
||||
) {
|
||||
dotNode = expr; // a?.b / a?.[i]
|
||||
recvNode = expr.expression;
|
||||
|
||||
@@ -7141,7 +7141,8 @@ function lowerProcessIpcSend(lowerer: Lowerer, call: ts.CallExpression): IrExpr
|
||||
while (info && info.base) info = info.base;
|
||||
if (!info || info.def.name !== "%Error") return null;
|
||||
if (!lowerer.isStdlibMember(expr)) return null;
|
||||
const receiver = lowerer.lowerExpr(expr.expression);
|
||||
const rawReceiver = lowerer.lowerExpr(expr.expression);
|
||||
const receiver = rawReceiver.type.kind === "dyn" ? lowerer.coerceInto(expr.expression, rawReceiver, recvT) : rawReceiver;
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: expr.name.text === "stack" ? "error.stack" : expr.name.text === "cause" ? "error.cause" : "error.code",
|
||||
|
||||
@@ -48,7 +48,7 @@ import { fenceSymbolFieldCopy } from "./symbol-fields.js";
|
||||
import { lowerClassDataDescriptor, lowerClassDescriptorRead } from "./class-descriptors.js";
|
||||
import { classStaticDataFor } from "./class-static-data.js";
|
||||
import { jsBindingHasOpenWrites } from "./lower-stmts.js";
|
||||
import { functionCanFallThrough } from "../function-completion.js";
|
||||
import { functionCanReturnUndefined } from "../function-completion.js";
|
||||
import { checkedIterableSpread } from "./checked-iterable-spread.js";
|
||||
|
||||
export { bodyReadsArguments };
|
||||
@@ -1138,7 +1138,7 @@ function completeFuncValueArgs(
|
||||
const fallback = dynFallbackType(lowerer, decl, retTsType);
|
||||
return fallback?.kind === "promise" ? fallback : { kind: "promise", inner: DYN };
|
||||
}
|
||||
if (mappedReturn && isJsSourceFile(decl.getSourceFile()) && functionCanFallThrough(decl)) mappedReturn = lowerer.withUndefinedArmOf(mappedReturn) ?? mappedReturn;
|
||||
if (mappedReturn && isJsSourceFile(decl.getSourceFile()) && functionCanReturnUndefined(decl)) mappedReturn = lowerer.withUndefinedArmOf(mappedReturn) ?? mappedReturn;
|
||||
if (isJsSourceFile(decl.getSourceFile()) && mappedReturn?.kind === "void") {
|
||||
let returnsValue = false;
|
||||
const scan = (node: ts.Node): void => {
|
||||
@@ -1152,6 +1152,8 @@ function completeFuncValueArgs(
|
||||
if (isJsSourceFile(decl.getSourceFile()) && mappedReturn?.kind === "array") return DYN;
|
||||
if (isJsSourceFile(decl.getSourceFile()) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "record") return DYN;
|
||||
if (isJsSourceFile(decl.getSourceFile()) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "date") return DYN;
|
||||
if (isJsSourceFile(decl.getSourceFile()) && !decl.type && !hasExplicitJsDocReturn(decl) &&
|
||||
mappedReturn?.kind === "union" && lowerer.unions.get(mappedReturn.unionId)?.arms.some((arm) => arm.kind === "func")) return DYN;
|
||||
// JS inference assigns Number to arithmetic over any, although the
|
||||
// runtime operands can both be BigInts. Preserve that native result.
|
||||
if (isJsSourceFile(decl.getSourceFile()) && !decl.type && !hasExplicitJsDocReturn(decl) &&
|
||||
@@ -2379,6 +2381,11 @@ function runtimeOptionalHofGenericBinding(
|
||||
if (elem && (elem.flags & ts.TypeFlags.Any) !== 0) return null;
|
||||
}
|
||||
const mapped = lowerer.mapTypeOf(retTs);
|
||||
// Mixed inferred results can select a closure or a class instance.
|
||||
// Their callable ABI is settled by the lowered body, including native
|
||||
// classes whose reflected calls return checked values.
|
||||
if (mapped?.kind === "union" && !hasExplicitJsDocReturn(info.decl) &&
|
||||
lowerer.unions.get(mapped.unionId)?.arms.some((arm) => arm.kind === "func")) return null;
|
||||
if (mapped?.kind === "symbol" && !hasExplicitJsDocReturn(info.decl)) return null;
|
||||
if (mapped?.kind === "date" && !hasExplicitJsDocReturn(info.decl)) return DYN;
|
||||
if (!hasExplicitJsDocReturn(info.decl) && info.decl.body) {
|
||||
@@ -6263,7 +6270,7 @@ function lowerOptionalStringNumber(
|
||||
}
|
||||
if (
|
||||
spreadParts.length > 0 &&
|
||||
spreadParts.every((p) => p.v.type.kind === "jsval") &&
|
||||
(spreadParts.every((p) => p.v.type.kind === "jsval") || callee.type.kind === "func" && callee.type.restAbi === "jsval") &&
|
||||
(callee.type.kind === "jsval" || callee.type.kind === "func")
|
||||
) {
|
||||
if (spreadParts.length !== 1 || parts[parts.length - 1]!.spreadOf === null) {
|
||||
@@ -6281,7 +6288,7 @@ function lowerOptionalStringNumber(
|
||||
const last = parts[parts.length - 1]!;
|
||||
// The spelling rides in `name` for V8's nullish spread-call
|
||||
// TypeError ("v is not iterable (cannot read property ...)").
|
||||
return { kind: "jsOp", op: "callSpread", name: last.spreadOf!.getText(), args: [f, preArr, last.v], type: JSVAL, loc };
|
||||
return { kind: "jsOp", op: "callSpread", name: last.spreadOf!.getText(), args: [f, preArr, lowerer.coerceInto(last.spreadOf!, last.v, JSVAL)], type: JSVAL, loc };
|
||||
}
|
||||
// No lane fits (a spread source outside both tiers, a callee neither
|
||||
// boxable nor marshalable, mixed dyn/jsval spreads): fence HERE — the
|
||||
@@ -6375,7 +6382,7 @@ function lowerOptionalStringNumber(
|
||||
!["call", "apply", "bind", "toString", "valueOf", "constructor"].includes(access.name.text) &&
|
||||
canBoxFuncIntoDyn(probed.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
|
||||
const loc = locOf(call);
|
||||
const fnName = jsFuncNameOf(access.expression);
|
||||
const fnName = jsFuncNameOf(access.expression, lowerer);
|
||||
const boxed: IrExpr = { kind: "dynFrom", value: probed, type: DYN, loc, ...(fnName !== null ? { fnName } : {}) };
|
||||
const local = lowerer.declareHiddenLocal("%callableReceiver", DYN);
|
||||
const receiver: IrExpr = { kind: "varRef", localId: local.id, type: DYN, loc };
|
||||
@@ -6398,7 +6405,7 @@ function lowerOptionalStringNumber(
|
||||
}
|
||||
if (probed?.type.kind === "union" && isJsSourceFile(call.getSourceFile()) &&
|
||||
lowerer.unions.get(probed.type.unionId)?.arms.every((arm) =>
|
||||
isUnitType(arm) || arm.kind === "object" || arm.kind === "record") && lowerer.dynConvertible(probed.type)) {
|
||||
isUnitType(arm) || arm.kind === "object" || arm.kind === "record" || arm.kind === "func") && lowerer.dynConvertible(probed.type)) {
|
||||
recv = lowerer.coerceToExpected(probed, DYN);
|
||||
} else {
|
||||
if (probed?.type.kind !== "dyn") return null;
|
||||
@@ -6499,6 +6506,9 @@ function lowerOptionalStringNumber(
|
||||
// with the original receiver rather than a materialized property view.
|
||||
const callbackProperty = (() => {
|
||||
const prop = lowerer.checker.getPropertyOfType(recvTs, access.name.text);
|
||||
const mapped = lowerer.mapTypeOf(recvTs);
|
||||
if (mapped?.kind === "union" && lowerer.unions.get(mapped.unionId)?.arms.every((arm) =>
|
||||
arm.kind === "object" && lowerer.classes.get(arm.className)?.fields.has(access.name.text))) return true;
|
||||
return prop !== undefined && lowerer.checker.declarationsOf(prop).some((decl) =>
|
||||
ts.isPropertyDeclaration(decl) || ts.isPropertySignature(decl));
|
||||
})();
|
||||
@@ -6534,17 +6544,23 @@ function lowerOptionalStringNumber(
|
||||
type: DYN,
|
||||
loc: locOf(access),
|
||||
};
|
||||
const calleeLocal = lowerer.declareHiddenLocal("%dynCallCallee", DYN);
|
||||
const callee = varRef(calleeLocal.id, DYN, loc);
|
||||
const stmts: IrStmt[] = [
|
||||
{ kind: "varDecl", localId: receiverLocal.id, init: recv, loc },
|
||||
{ kind: "varDecl", localId: calleeLocal.id, init: member, loc },
|
||||
];
|
||||
if (call.arguments.some(ts.isSpreadElement)) {
|
||||
const spread = lowerSpreadArgsCall(lowerer, call, member, loc);
|
||||
const spread = lowerSpreadArgsCall(lowerer, call, callee, loc);
|
||||
if (spread?.kind === "dynCall") return { kind: "seqExpr",
|
||||
stmts: [{ kind: "varDecl", localId: receiverLocal.id, init: recv, loc }],
|
||||
stmts,
|
||||
result: { ...spread, receiver }, type: DYN, loc };
|
||||
lowerer.unsupported("SC1090", call, "spread arguments in calls through 'unknown' values");
|
||||
}
|
||||
const args = call.arguments.map((a) => lowerer.lowerExprExpecting(a, DYN));
|
||||
return {
|
||||
kind: "seqExpr", stmts: [{ kind: "varDecl", localId: receiverLocal.id, init: recv, loc }],
|
||||
result: { kind: "dynCall", callee: member, receiver, calleeName: access.getText(), args, type: DYN, loc }, type: DYN, loc,
|
||||
kind: "seqExpr", stmts,
|
||||
result: { kind: "dynCall", callee, receiver, calleeName: access.getText(), args, type: DYN, loc }, type: DYN, loc,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8379,7 +8395,7 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
value: {
|
||||
kind: "dynCall",
|
||||
callee: { kind: "varRef", localId: cbLocal.id, type: DYN, loc },
|
||||
calleeName: jsFuncNameOf(call.arguments[0]!) ?? "onFulfilled",
|
||||
calleeName: jsFuncNameOf(call.arguments[0]!, lowerer) ?? "onFulfilled",
|
||||
args: handlerArgs,
|
||||
type: DYN,
|
||||
loc,
|
||||
@@ -8510,6 +8526,14 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
|
||||
if (member === "finally") {
|
||||
const cb = lowerer.lowerExpr(call.arguments[0]!);
|
||||
if (lowerer.dynConvertible(promT) && lowerer.dynConvertible(cb.type)) {
|
||||
const result: IrExpr = {
|
||||
kind: "dynInvoke", recv: lowerer.coerceToExpected(receiver, DYN),
|
||||
method: "finally", calleeName: access.getText(),
|
||||
args: [lowerer.coerceToExpected(cb, DYN)], type: DYN, loc,
|
||||
};
|
||||
return lowerer.coerceToExpected(result, promT);
|
||||
}
|
||||
if (cb.type.kind !== "func" || cb.type.params.length !== 0 || cb.type.ret.kind !== "void") {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
@@ -8621,7 +8645,7 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
value: {
|
||||
kind: "dynCall",
|
||||
callee: { kind: "varRef", localId: cbLocal.id, type: DYN, loc },
|
||||
calleeName: jsFuncNameOf(call.arguments[0]!) ?? "onRejected",
|
||||
calleeName: jsFuncNameOf(call.arguments[0]!, lowerer) ?? "onRejected",
|
||||
args: [
|
||||
{
|
||||
kind: "caughtToDyn",
|
||||
@@ -9482,7 +9506,7 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
!call.arguments.some(ts.isSpreadElement) && !lowerer.dynamic) {
|
||||
let object = lowerer.lowerExpr(call.arguments[0]!);
|
||||
if (object.type.kind === "func") {
|
||||
const fnName = jsFuncNameOf(call.arguments[0]!);
|
||||
const fnName = jsFuncNameOf(call.arguments[0]!, lowerer);
|
||||
object = { kind: "dynFrom", value: object, type: DYN, loc: locOf(call),
|
||||
...(fnName !== null ? { fnName } : {}) };
|
||||
}
|
||||
@@ -9892,7 +9916,7 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
target = lowerer.coerceToExpected(target, DYN);
|
||||
}
|
||||
if (target?.type.kind === "func" && lowerer.dynConvertible(target.type)) {
|
||||
const fnName = jsFuncNameOf(call.arguments[0]!);
|
||||
const fnName = jsFuncNameOf(call.arguments[0]!, lowerer);
|
||||
target = { kind: "dynFrom", value: target, type: DYN, loc: locOf(call.arguments[0]!), ...(fnName !== null ? { fnName } : {}) };
|
||||
}
|
||||
const descriptorPrimitive = member === "getOwnPropertyDescriptor" && target &&
|
||||
@@ -9932,7 +9956,7 @@ export function lowerPromiseMethodCall(lowerer: Lowerer, call: ts.CallExpression
|
||||
target && target.type.kind === "func" &&
|
||||
canBoxFuncIntoDyn(target.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))
|
||||
) {
|
||||
const fnName = jsFuncNameOf(call.arguments[0]!);
|
||||
const fnName = jsFuncNameOf(call.arguments[0]!, lowerer);
|
||||
target = { kind: "dynFrom", value: target, type: DYN, loc: locOf(call.arguments[0]!), ...(fnName !== null ? { fnName } : {}) };
|
||||
}
|
||||
if (target && isUnitType(target.type)) {
|
||||
@@ -11524,8 +11548,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I
|
||||
const arms = lowerer.unions.get(mappedReceiver.unionId)?.arms;
|
||||
if (isJsSourceFile(call.getSourceFile()) && arms?.every((arm) => arm.kind === "object" &&
|
||||
!lowerer.classes.get(arm.className)?.def.runtime) && lowerer.dynConvertible(mappedReceiver)) {
|
||||
const receiver = lowerer.coerceInto(access.expression, lowerer.lowerExpr(access.expression), DYN);
|
||||
return lowerDynDispatchMethodCall(lowerer, call, access, receiver, false);
|
||||
return lowerDynReceiverMethodCall(lowerer, call, access);
|
||||
}
|
||||
}
|
||||
const receiverIr = mappedReceiver?.kind === "object"
|
||||
|
||||
@@ -831,8 +831,21 @@ function returnsWidenedJsField(lowerer: Lowerer, decl: ts.ClassDeclaration | ts.
|
||||
const widened = (expression: ts.Expression): boolean => {
|
||||
if (ts.isParenthesizedExpression(expression)) return widened(expression.expression);
|
||||
if (ts.isConditionalExpression(expression)) return widened(expression.whenTrue) || widened(expression.whenFalse);
|
||||
if (ts.isCallExpression(expression)) {
|
||||
const callee = expression.expression;
|
||||
const symbol = ts.isPropertyAccessExpression(callee) ? lowerer.checker.getSymbolAtLocation(callee.name)
|
||||
: ts.isElementAccessExpression(callee) ? lowerer.checker.getPropertyOfType(lowerer.typeOf(callee.expression), lowerer.foldedStringKeyOf(callee.argumentExpression) ?? "")
|
||||
: undefined;
|
||||
if (symbol && lowerer.checker.declarationsOf(symbol).some((declaration) =>
|
||||
ts.isMethodDeclaration(declaration) && lowerer.checkedVirtualJsMethods.has(declaration))) return true;
|
||||
}
|
||||
const name = ts.isPropertyAccessExpression(expression) ? expression.name.text
|
||||
: ts.isElementAccessExpression(expression) ? lowerer.foldedStringKeyOf(expression.argumentExpression) : null;
|
||||
if (ts.isPropertyAccessExpression(expression)) {
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(expression.name);
|
||||
if (symbol && lowerer.checker.declarationsOf(symbol).some((declaration) =>
|
||||
(ts.isGetAccessorDeclaration(declaration) || ts.isSetAccessorDeclaration(declaration)) && lowerer.checkedVirtualJsAccessors.has(declaration))) return true;
|
||||
}
|
||||
if (name !== null && (ts.isPropertyAccessExpression(expression) || ts.isElementAccessExpression(expression)) &&
|
||||
expression.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
||||
return fields.get(name)?.kind === "dyn" || inheritedJsFieldType(lowerer, decl, name, ret).kind === "dyn";
|
||||
@@ -1736,6 +1749,9 @@ export function collectClassShapeInner(lowerer: Lowerer, decl: ts.ClassLikeDecla
|
||||
member.initializer &&
|
||||
member.postfixToken?.kind !== ts.SyntaxKind.QuestionToken
|
||||
) {
|
||||
const initializerSymbol = ts.isIdentifier(member.initializer) ? lowerer.resolveValueSymbol(member.initializer) : null;
|
||||
// Enum namespace aliases are erased; member reads fold through the checker.
|
||||
if (initializerSymbol && (initializerSymbol.flags & ts.SymbolFlags.Enum) !== 0) continue;
|
||||
const type = lowerer.irTypeOf(member.name);
|
||||
if (type.kind === "void") lowerer.badType(member.name, lowerer.typeOf(member.name));
|
||||
if (type.kind === "dyn" && !isJsSourceFile(member.getSourceFile()) &&
|
||||
@@ -2420,6 +2436,7 @@ export function collectClassShapeInner(lowerer: Lowerer, decl: ts.ClassLikeDecla
|
||||
}
|
||||
if (
|
||||
overridden &&
|
||||
member.asteriskToken === undefined &&
|
||||
(overridden.sig.params.length !== shapes.length ||
|
||||
!overridden.sig.params.every((p, i) => typeEquals(p.type, shapes[i]!.type)) ||
|
||||
!typeEquals(overridden.sig.ret, ft.ret))
|
||||
@@ -2427,7 +2444,7 @@ export function collectClassShapeInner(lowerer: Lowerer, decl: ts.ClassLikeDecla
|
||||
const fencedJsOverride =
|
||||
isJsSourceFile(member.getSourceFile()) &&
|
||||
overridden.declarer.decl !== null && isJsSourceFile(overridden.declarer.decl.getSourceFile()) &&
|
||||
member.asteriskToken === undefined && overridden.sig.gen === undefined;
|
||||
overridden.sig.gen === undefined;
|
||||
if (fencedJsOverride) {
|
||||
// A dormant JS method must not poison the whole class. Keep
|
||||
// the inherited call/dispatch ABI and refuse at method entry;
|
||||
@@ -2548,6 +2565,10 @@ export function collectClassShapeInner(lowerer: Lowerer, decl: ts.ClassLikeDecla
|
||||
// tsc rejects optional/default/rest setter params (TS1051-53).
|
||||
sig = { params: lowerer.paramShapes(member.parameters), ret: VOID };
|
||||
}
|
||||
if (lowerer.checkedVirtualJsAccessors.has(member)) {
|
||||
if (isGet) sig.ret = DYN;
|
||||
else for (const parameter of sig.params) parameter.type = DYN;
|
||||
}
|
||||
// One property, ONE type: tsc (5.1+) admits get/set pairs with
|
||||
// unrelated annotated types; a property slot here has a single
|
||||
// IR type, so the pair must agree exactly.
|
||||
@@ -3823,7 +3844,7 @@ export function staticFieldWriteTarget(lowerer: Lowerer, access: ts.PropertyAcce
|
||||
// JavaScript often assigns public statics after the class declaration.
|
||||
// Their storage stays mutable and checked, while reads retain the
|
||||
// checker's concrete type instead of losing the method receiver.
|
||||
const type = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
||||
const type = isJsSourceFile(expr.getSourceFile()) ? null : lowerer.mapTypeOf(lowerer.typeOf(expr));
|
||||
return type ? lowerer.coerceToExpected(value, type) : value;
|
||||
}
|
||||
return null;
|
||||
@@ -4650,6 +4671,35 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou
|
||||
});
|
||||
const visit = (node: ts.Node): void => {
|
||||
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
|
||||
const accessors = node.members.filter((member): member is ts.GetAccessorDeclaration | ts.SetAccessorDeclaration =>
|
||||
(ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) && ts.isIdentifier(member.name) &&
|
||||
!member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword));
|
||||
if (node.heritageClauses?.length && accessors.length && isJsSourceFile(node.getSourceFile())) {
|
||||
const symbol = lowerer.typeOf(node).getSymbol();
|
||||
const instance = symbol ? lowerer.checker.getDeclaredTypeOfSymbol(symbol) : null;
|
||||
const seen = new Set<ts.Type>();
|
||||
const markAccessors = (type: ts.Type): void => {
|
||||
const target = type.isTypeReference() ? type.getTarget() : type;
|
||||
if (!target?.isClassOrInterface() || seen.has(target)) return;
|
||||
seen.add(target);
|
||||
for (const base of lowerer.checker.getBaseTypes(target)) {
|
||||
for (const member of accessors) {
|
||||
if (!ts.isIdentifier(member.name)) continue;
|
||||
const property = lowerer.checker.getPropertyOfType(base, member.name.text);
|
||||
for (const declaration of property ? lowerer.checker.declarationsOf(property) : []) {
|
||||
if ((ts.isGetAccessorDeclaration(declaration) || ts.isSetAccessorDeclaration(declaration)) && isJsSourceFile(declaration.getSourceFile())) {
|
||||
lowerer.checkedVirtualJsAccessors.add(declaration);
|
||||
lowerer.checkedVirtualJsAccessors.add(member);
|
||||
for (const twin of node.members) if ((ts.isGetAccessorDeclaration(twin) || ts.isSetAccessorDeclaration(twin)) &&
|
||||
ts.isIdentifier(twin.name) && twin.name.text === member.name.text) lowerer.checkedVirtualJsAccessors.add(twin);
|
||||
}
|
||||
}
|
||||
}
|
||||
markAccessors(base);
|
||||
}
|
||||
};
|
||||
if (instance) markAccessors(instance);
|
||||
}
|
||||
const members = node.members.filter((member): member is ts.MethodDeclaration =>
|
||||
ts.isMethodDeclaration(member) && ts.isIdentifier(member.name) &&
|
||||
!member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword),
|
||||
@@ -6027,6 +6077,14 @@ function assignedThisFieldType(lowerer: Lowerer, expr: ts.NewExpression): IrType
|
||||
|
||||
export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
const loc = locOf(expr);
|
||||
let intlConstructor = expr.expression;
|
||||
if (ts.isIdentifier(intlConstructor)) {
|
||||
const symbol = lowerer.resolveValueSymbol(intlConstructor);
|
||||
const declaration = symbol ? lowerer.checker.valueDeclarationOf(symbol) : undefined;
|
||||
if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer) intlConstructor = declaration.initializer;
|
||||
}
|
||||
const intl = ts.isPropertyAccessExpression(intlConstructor) ? lowerer.stdlibGlobalMember(intlConstructor, "Intl") : null;
|
||||
if (intl && intl !== "Segmenter") lowerer.noLowering(`new Intl.${intl}`, expr, undefined, lowerer.checker.getSymbolAtLocation(expr.expression));
|
||||
const instanceConstructor = lowerInstanceConstructorNew(lowerer, expr);
|
||||
if (instanceConstructor) return instanceConstructor;
|
||||
const objectFactory = lowerObjectFactoryNew(lowerer, expr);
|
||||
|
||||
@@ -6360,6 +6360,8 @@ function mapFromSeedValue(lowerer: Lowerer, seed: IrExpr, mapT: IrType & { kind:
|
||||
const argNode = call.arguments[0]!;
|
||||
const argIr = lowerer.mapTypeOf(lowerer.typeOf(argNode));
|
||||
if (argIr?.kind !== "array") return null;
|
||||
const represented = tryLowerExpression(lowerer, argNode);
|
||||
if (represented && !typeEquals(represented.type, argIr)) return null;
|
||||
// `Object.fromEntries(rows)` over a `string[][]` VALUE — the env-line
|
||||
// idiom (`envArray.map((env) => env.split('='))`). The checker has no
|
||||
// tuple to type here (string[] misses the lib's [PropertyKey, T]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { InternalCompilerError } from "../../errors.js";
|
||||
import { SYMBOL_T } from "../../ir/ir.js";
|
||||
import { literalValues } from "../literal-values.js";
|
||||
import { literalUnionArm } from "../union-discriminants.js";
|
||||
/* Expression lowering: the expression dispatch (lowerExpr), literals
|
||||
@@ -2691,7 +2692,10 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
// objects). Extract at the consuming operation, not at the read.
|
||||
if (isJsSourceFile(node.getSourceFile()) && ts.isPropertyAccessExpression(node)) {
|
||||
const receiver = lowerer.mapTypeOf(lowerer.typeOf(node.expression));
|
||||
if (receiver?.kind === "object" && lowerer.classes.get(receiver.className)?.fields.get(node.name.text)?.kind === "dyn") return expr;
|
||||
if (receiver?.kind === "object") {
|
||||
const info = lowerer.classes.get(receiver.className);
|
||||
if (info?.fields.get(node.name.text)?.kind === "dyn" || lowerer.findMethodOn(info ?? null, `get:${node.name.text}`)?.sig.ret.kind === "dyn") return expr;
|
||||
}
|
||||
if (receiver?.kind === "classval") {
|
||||
const info = lowerer.classes.get(receiver.className);
|
||||
if (info && hasRuntimeStatics(info)) return expr;
|
||||
@@ -2718,6 +2722,8 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
}
|
||||
if (isJsSourceFile(node.getSourceFile()) && ts.isIdentifier(node)) {
|
||||
const symbol = lowerer.resolveValueSymbol(node);
|
||||
if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => ts.isParameter(decl) &&
|
||||
ts.isMethodDeclaration(decl.parent) && lowerer.checkedVirtualJsMethods.has(decl.parent))) return expr;
|
||||
if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) =>
|
||||
ts.isParameter(decl) && ts.isConstructorDeclaration(decl.parent))) return expr;
|
||||
if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) =>
|
||||
@@ -3917,7 +3923,7 @@ function lowerPromiseThenPresence(
|
||||
// (the child.stdout pattern); a checker-narrowed read extracts
|
||||
// through maybeNarrow like any union member.
|
||||
if (name === "description") {
|
||||
const receiver = lowerer.lowerExpr(expr.expression);
|
||||
const receiver = lowerer.coerceToExpected(lowerer.lowerExpr(expr.expression), SYMBOL_T);
|
||||
const type: IrType = { kind: "union", unionId: lowerer.unions.intern([STRING, UNDEFINED_T]) };
|
||||
return { kind: "libCall", fn: "sym.desc", args: [receiver], type, loc };
|
||||
}
|
||||
@@ -10101,6 +10107,13 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE
|
||||
if (receiverIr?.kind === "record") {
|
||||
const shape = lowerer.shapes.get(receiverIr.shapeId);
|
||||
if (shape?.indexValue && !shape.fields.some((f) => f.name === expr.name.text)) {
|
||||
const actual = tryLowerExpression(lowerer, expr.expression);
|
||||
if (actual?.type.kind === "dyn") return { kind: "dynKeyGet", value: actual,
|
||||
key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) }, type: DYN, loc: locOf(expr) };
|
||||
if (actual?.type.kind === "record") {
|
||||
const field = lowerer.shapes.get(actual.type.shapeId)?.fields.find((field) => field.name === expr.name.text);
|
||||
if (field) return { kind: "recordGet", obj: actual, shapeId: actual.type.shapeId, field: field.name, type: field.type, loc: locOf(expr) };
|
||||
}
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
expr,
|
||||
@@ -10489,6 +10502,7 @@ function representedClassFieldTarget(
|
||||
* receivers) a declared accessor property. Returns the pieces of a
|
||||
* fieldSet/recordSet/accessor-call (minus value/kind) or null. */
|
||||
export function fieldTarget(lowerer: Lowerer, access: ts.PropertyAccessExpression): FieldTarget | null {
|
||||
if (lowerer.cjsLocalModuleBindingOf(access.expression)) return null;
|
||||
if (lowerer.chainBlocked(access)) return null;
|
||||
if (access.name.text === "stackTraceLimit" && lowerer.isStdlibGlobal(access.expression, "Error")) {
|
||||
return { container: "errorStackLimit", obj: { kind: "numLit", value: 0, type: F64, loc: locOf(access) }, field: "stackTraceLimit", fieldType: F64 };
|
||||
@@ -10593,7 +10607,11 @@ function representedClassFieldTarget(
|
||||
return declaredArms.every((a) => ivArms.some((b) => typeEquals(a, b)));
|
||||
};
|
||||
if (!nameSym || nameSym.name === ts.InternalSymbolName.Index || canonicalized()) {
|
||||
const obj = lowerer.lowerExpr(access.expression);
|
||||
let obj = lowerer.lowerExpr(access.expression);
|
||||
if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) {
|
||||
const present = lowerer.stripUndefinedArm(obj.type);
|
||||
obj = present.kind === "record" ? lowerer.runtimeOptionalPropertyReceiver(access.expression, obj, present, access.name.text) ?? obj : lowerer.maybeNarrow(obj, access.expression);
|
||||
}
|
||||
if (obj.type.kind !== "record") return null;
|
||||
const actualShape = lowerer.shapes.get(obj.type.shapeId);
|
||||
if (!actualShape?.indexValue) return null;
|
||||
|
||||
@@ -736,6 +736,7 @@ export function appendForkModules(
|
||||
records: IrRecordShape[];
|
||||
unions: IrUnionDef[];
|
||||
} {
|
||||
const valueInstanceOfRoots = new Set<string>();
|
||||
const classNames = new Set<string>();
|
||||
const shapeIds = new Set<string>();
|
||||
const unionIds = new Set<string>();
|
||||
@@ -769,6 +770,16 @@ export function appendForkModules(
|
||||
}
|
||||
if (node === null || typeof node !== "object") return;
|
||||
const rec = node as Record<string, unknown>;
|
||||
if (rec["kind"] === "instanceOfValue") {
|
||||
const expression = node as Extract<IrExpr, { kind: "instanceOfValue" }>;
|
||||
for (const type of [expression.value.type, expression.classValue.type]) {
|
||||
if (type.kind !== "object" && type.kind !== "classval") continue;
|
||||
let root = lowerer.classes.get(type.className);
|
||||
if (!root) continue;
|
||||
while (root.base) root = root.base;
|
||||
valueInstanceOfRoots.add(root.def.name);
|
||||
}
|
||||
}
|
||||
if (rec["kind"] === "dynFrom" && boxedChild((rec["value"] as IrExpr).type)) {
|
||||
// A generic child can expose native stdio without importing stream.
|
||||
visit([{ className: "%Readable" }, { className: "%Writable" }]);
|
||||
@@ -843,7 +854,7 @@ export function appendForkModules(
|
||||
let root = info;
|
||||
while (root.base) root = root.base;
|
||||
const wholeTree = (c: ClassInfo): void => {
|
||||
if (lowerer.classCanBeConstructed(c)) visit([{ className: c.def.name }]);
|
||||
if (valueInstanceOfRoots.has(root.def.name) || lowerer.classCanBeConstructed(c)) visit([{ className: c.def.name }]);
|
||||
for (const s of c.subclasses) wholeTree(s);
|
||||
};
|
||||
wholeTree(root);
|
||||
@@ -1093,6 +1104,9 @@ export function collectGlobals(lowerer: Lowerer, sf: ts.SourceFile, topStmts: ts
|
||||
}
|
||||
let type = lowerer.mapTypeOf(tsType) ?? dynFallbackType(lowerer, stmt.expression, tsType);
|
||||
if (type === null) lowerer.badType(stmt.expression, tsType);
|
||||
// Inferred JavaScript factory exports keep their original value,
|
||||
// including callable objects decorated with Object.assign.
|
||||
if (isJsSourceFile(sf) && ts.isCallExpression(stmt.expression) && type.kind === "record" && lowerer.dynConvertible(type)) type = DYN;
|
||||
// `export default undefined` — the unit-only union, like any
|
||||
// unit-only binding (`export default null` maps via mapType).
|
||||
if (type.kind === "void" && isUnitOnlyTsType(tsType)) {
|
||||
@@ -1136,34 +1150,22 @@ export function collectGlobals(lowerer: Lowerer, sf: ts.SourceFile, topStmts: ts
|
||||
const strict = lowerer.checker.getTypeOfSymbol(symbol);
|
||||
const t = lowerer.mapTypeOf(strict) ?? dynFallbackType(lowerer, nameNode, strict);
|
||||
if (!t || t.kind === "void") lowerer.badType(typeNode, strict);
|
||||
// `module.exports.Strings = Strings` naming a dyn-HOLDING
|
||||
// const (the JS file-scope object-literal identity story): the
|
||||
// export ALIASES the const's own dyn global — one storage, one
|
||||
// owner, identity by construction (a record-typed second slot
|
||||
// would either copy or mismatch the dyn value — that mismatch
|
||||
// was an ICE; a second same-named global double-released at
|
||||
// teardown). The statement's assign degenerates to a harmless
|
||||
// self-assign. Mutable (`let`) sources keep the registration
|
||||
// below: Node copies the VALUE at this statement, and separate
|
||||
// storage is exactly that copy.
|
||||
// An immutable export source shares its live storage and native
|
||||
// callable ABI. Mutable sources need a separate snapshot slot:
|
||||
// later writes to the binding do not change the exported value.
|
||||
{
|
||||
let rhs: ts.Node = typeNode;
|
||||
while (ts.isParenthesizedExpression(rhs)) rhs = rhs.expression;
|
||||
if (ts.isIdentifier(rhs)) {
|
||||
const vSym = lowerer.checker.getSymbolAtLocation(rhs);
|
||||
const vG = vSym && lowerer.globalsBySymbol.get(vSym);
|
||||
if (vG?.type.kind === "dyn") {
|
||||
if (vG) {
|
||||
if (!vG.mutable) {
|
||||
lowerer.globalsBySymbol.set(symbol, vG);
|
||||
for (const d of lowerer.checker.declarationsOf(symbol)) lowerer.globalsByDeclNode.set(d, vG);
|
||||
return;
|
||||
}
|
||||
// A mutable (`let`) dyn source: separate DYN storage IS
|
||||
// Node's copy-of-the-reference at this statement (later
|
||||
// reassignments of the let stay invisible through the
|
||||
// export). The id must not collide with the let's own
|
||||
// `%g.<tag><name>` global.
|
||||
const g: IrGlobal = { id: `%g.${tag}%export.${name}`, name, type: DYN, mutable: false };
|
||||
const g: IrGlobal = { id: `%g.${tag}%export.${name}`, name, type: vG.type, mutable: false };
|
||||
lowerer.globalsBySymbol.set(symbol, g);
|
||||
for (const d of lowerer.checker.declarationsOf(symbol)) lowerer.globalsByDeclNode.set(d, g);
|
||||
lowerer.globalsList.push(g);
|
||||
@@ -1171,7 +1173,7 @@ export function collectGlobals(lowerer: Lowerer, sf: ts.SourceFile, topStmts: ts
|
||||
}
|
||||
}
|
||||
}
|
||||
const g: IrGlobal = { id: `%g.${tag}${name}`, name, type: t, mutable: false };
|
||||
const g: IrGlobal = { id: `%g.${tag}%export.${name}`, name, type: t, mutable: false };
|
||||
lowerer.globalsBySymbol.set(symbol, g);
|
||||
// Importer aliases resolve to a DISTINCT late-bound symbol with
|
||||
// the same declaration — key the node too (globalOf's fallback).
|
||||
@@ -1816,6 +1818,18 @@ export function collectGlobals(lowerer: Lowerer, sf: ts.SourceFile, topStmts: ts
|
||||
!decl.initializer.typeParameters?.length) {
|
||||
type = lowerer.lambdaSignature(decl.initializer).funcType;
|
||||
}
|
||||
// Namespace aliases of inferred helpers carry the helper's
|
||||
// settled callable, whose return can differ from checker inference.
|
||||
// Keep a shared checked slot before any consumer specializes it.
|
||||
if (isJsSourceFile(sf) && !decl.type && !hasJsTypeAnnotation(decl) && type.kind === "func" &&
|
||||
decl.initializer && ts.isPropertyAccessExpression(decl.initializer)) {
|
||||
const member = nsMemberIdentOf(lowerer, decl.initializer);
|
||||
if (member && lowerer.genericFnOf(member)?.implicitParams) type = DYN;
|
||||
else if (member) {
|
||||
const source = lowerer.globalOf(member);
|
||||
if (source?.type.kind === "func") type = source.type;
|
||||
}
|
||||
}
|
||||
if (isJsSourceFile(sf) && !decl.type && !hasJsTypeAnnotation(decl) &&
|
||||
decl.initializer && ts.isCallExpression(decl.initializer) && type.kind === "union") {
|
||||
const arms = lowerer.unions.get(type.unionId)?.arms;
|
||||
|
||||
@@ -585,7 +585,9 @@ export function provenanceElidedConstDecl(lowerer: Lowerer, decl: ts.VariableDec
|
||||
if (idx < entry.index || entry.ctx === lowerer.ctx || entry.ctx.tdzPredeclared.has(symbol)) return false;
|
||||
let type = lowerer.irTypeOf(decl.name);
|
||||
if (isJsSourceFile(decl.getSourceFile()) && !decl.type && !hasJsTypeAnnotation(decl) &&
|
||||
ts.isObjectLiteralExpression(decl.initializer) && lowerer.dynConvertible(type)) type = DYN;
|
||||
(ts.isObjectLiteralExpression(decl.initializer) ||
|
||||
(ts.isCallExpression(decl.initializer) && ts.isPropertyAccessExpression(decl.initializer.expression) &&
|
||||
decl.initializer.expression.name.text === "assign" && lowerer.isStdlibGlobal(decl.initializer.expression.expression, "Object"))) && lowerer.dynConvertible(type)) type = DYN;
|
||||
if (!TDZ_KINDS.has(type.kind) || isUnitType(type)) return false;
|
||||
const name = decl.name.text;
|
||||
const count = entry.ctx.localCounters.get(name) ?? 0;
|
||||
@@ -1016,12 +1018,9 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt
|
||||
// (JS), which the undefined arm carries — tsc only allows the bare
|
||||
// form when the return type includes undefined/void. A CHECKED-
|
||||
// DYNAMIC return slot holds the dyn undefined directly (the
|
||||
// appendImplicitUndefinedReturn rule). What remains is JS-only —
|
||||
// a JSDoc return claim the body contradicts (ms's parse: `@return
|
||||
// {Number}` with a bare early `return;`): no undefined fits the
|
||||
// declared representation, so the statement fences honestly (the
|
||||
// per-statement JS deferral — the claimed paths still run) instead
|
||||
// of slipping an empty return past the validator.
|
||||
// appendImplicitUndefinedReturn rule). JavaScript signatures include
|
||||
// observed empty returns even when JSDoc omits undefined. Any remaining
|
||||
// incompatible boundary keeps the ordinary per-statement refusal.
|
||||
let value: IrExpr | null = null;
|
||||
if (stmt.expression) {
|
||||
return lowerer.lowerReturnStmt(stmt.expression, locOf(stmt));
|
||||
@@ -3713,7 +3712,8 @@ export function lowerVarDecl(lowerer: Lowerer, decl: ts.VariableDeclaration, isL
|
||||
: (declSymbol ? lowerer.globalsBySymbol.get(declSymbol) : undefined);
|
||||
if (g) {
|
||||
let promotedGlobal = lowerer.runtimeOptionalBindingType(decl.name, g.type);
|
||||
if (g.type.kind === "func" && decl.initializer !== undefined && ts.isIdentifier(decl.initializer)) {
|
||||
if (g.type.kind === "func" && !decl.type && !hasJsTypeAnnotation(decl) &&
|
||||
decl.initializer !== undefined && ts.isIdentifier(decl.initializer)) {
|
||||
const sig = lowerer.fnSigOf(decl.initializer);
|
||||
if (sig) {
|
||||
promotedGlobal = {
|
||||
@@ -3745,6 +3745,8 @@ export function lowerVarDecl(lowerer: Lowerer, decl: ts.VariableDeclaration, isL
|
||||
const raw = lowerVariableInitializer(lowerer, decl.initializer,
|
||||
g.type.kind === "dyn" && (isNativeBuiltinValueInitializer(lowerer, decl.initializer) ||
|
||||
ts.isArrayLiteralExpression(decl.initializer) || ts.isObjectLiteralExpression(decl.initializer)) ? DYN : undefined);
|
||||
if (isJsSourceFile(decl.getSourceFile()) && !decl.type && !hasJsTypeAnnotation(decl) && raw.type.kind === "dyn" &&
|
||||
(g.type.kind === "record" || g.type.kind === "object")) g.type = DYN;
|
||||
g.type = inferredOptionalRecordType(lowerer, decl, g.type, raw.type);
|
||||
const arithmetic = raw.type.kind === "union"
|
||||
? lowerer.unions.get(raw.type.unionId)?.arms
|
||||
@@ -4000,6 +4002,8 @@ export function lowerVarDecl(lowerer: Lowerer, decl: ts.VariableDeclaration, isL
|
||||
!decl.type && !hasJsTypeAnnotation(decl) && type.rest === true && type.restAbi === undefined)) {
|
||||
type = DYN;
|
||||
}
|
||||
if (isJsSourceFile(decl.getSourceFile()) && !decl.type && !hasJsTypeAnnotation(decl) && init.type.kind === "dyn" &&
|
||||
(type?.kind === "record" || type?.kind === "object")) type = DYN;
|
||||
// A checker-`any` CONST whose initializer lowered to a STATIC type
|
||||
// (`const name = rawName ? rawName.replace(...) : null` — the
|
||||
// dyn-receiver machinery and the any-ternary join answer static IR):
|
||||
|
||||
@@ -1257,6 +1257,7 @@ function lowerEosCallback(
|
||||
if (v.type.kind === "dyn") return { cb: v, dyn: true };
|
||||
} else if (t?.kind === "func") {
|
||||
const cb = lowerer.lowerExpr(node);
|
||||
if (cb.type.kind === "dyn") return { cb, dyn: true };
|
||||
if (cb.type.kind === "func" && canBoxFuncIntoDyn(cb.type, getRecord, getUnion)) {
|
||||
return { cb: { kind: "dynFrom", value: cb, type: DYN, loc: locOf(node) }, dyn: true };
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ import { lowerNodeTestModuleCall, lowerTestDirectCall, lowerTestMethodCall, lowe
|
||||
import { lowerAssertModuleCall, lowerAssertDirectCall } from "./lower-assert.js";
|
||||
import { lowerUtilModuleCall } from "./lower-inspect.js";
|
||||
import { lowerComptime, comptimeBakeable, rejectComptimeCaptures, comptimeValueToIr } from "./lower-comptime.js";
|
||||
import { lowerStmts, noteBlockedBindings, isBlockedBinding, lowerScopedBlock, predeclareForwardCapture, predeclareForwardFnDecl, predeclareForwardVar, lowerStmt, lowerVarStatement, lowerDestructuringDecl, lowerDestructuringAssignParts, lowerBindingPattern, lowerJsvalBindingPattern, checkBindingElement, bindPatternTarget, isParseArgsDynCheckerType, lowerVarDeclList, lowerVarDecl, lowerSwitch, lowerTry, lowerExprStatement, lowerForOf, lowerForStatement } from "./lower-stmts.js";
|
||||
import { lowerStmts, hasJsTypeAnnotation, noteBlockedBindings, isBlockedBinding, lowerScopedBlock, predeclareForwardCapture, predeclareForwardFnDecl, predeclareForwardVar, lowerStmt, lowerVarStatement, lowerDestructuringDecl, lowerDestructuringAssignParts, lowerBindingPattern, lowerJsvalBindingPattern, checkBindingElement, bindPatternTarget, isParseArgsDynCheckerType, lowerVarDeclList, lowerVarDecl, lowerSwitch, lowerTry, lowerExprStatement, lowerForOf, lowerForStatement } from "./lower-stmts.js";
|
||||
import { type FieldTarget, lowerExpr, maybeNarrow, lowerUnitComparison, lowerNullishCoalesce, lowerCondition, ensureBool, requireTruthyUnion, eqComparableUnion, lowerIntrinsicProperty, lowerArrayLiteral, lowerElementAccess, lowerElementWrite, lowerRecordKeyRead, ensureString, lowerTemplate, lowerAsExpression, lowerPrefixUnary, lowerBinary, lowerCaughtTypeofTest, caughtRead, caughtLocalOf, caughtToString, lowerInstanceOf, lowerRegexLiteral, lowerFieldRead, lowerUnionProperty, fieldTarget, fieldGetExpr, fieldSetStmt, lowerFieldCompound, uniqueSymbolKeyOf } from "./lower-exprs.js";
|
||||
import { finishOptionalChain, isOptionalChainTail, lowerOptionalChain } from "./expressions/optional-chains.js";
|
||||
import { foldedStringKeyOf, lowerDynObjectLiteral, lowerObjectLiteral, lowerShorthandValue, rejectThisInObjectMethod } from "./expressions/object-literals.js";
|
||||
@@ -1004,17 +1004,27 @@ function anyPiecedFuncType(lowerer: Lowerer, node: ts.Node, t: ts.Type): IrType
|
||||
return { kind: "func", params, ret };
|
||||
}
|
||||
|
||||
/** The best-effort JS `Function.prototype.name` of an expression flowing
|
||||
* into a dyn slot (the boxed function kind's inspect/error name):
|
||||
* identifier and property reads answer the referenced NAME (a
|
||||
* REFERENCE-SITE approximation of JS's creation-site naming — an aliased
|
||||
* binding reports the alias; SEMANTICS.md), named function expressions
|
||||
/** The JS `Function.prototype.name` of an expression flowing into a dyn
|
||||
* slot. Immutable identifier aliases retain their creation-site name; property
|
||||
* reads answer the referenced name. Named function expressions
|
||||
* their own name, anonymous function/arrow expressions their
|
||||
* NamedEvaluation home (a variable initializer or property assignment).
|
||||
* Null when nothing names the value (the box stays anonymous). */
|
||||
export function jsFuncNameOf(node: ts.Node): string | null {
|
||||
export function jsFuncNameOf(node: ts.Node, lowerer?: Lowerer): string | null {
|
||||
let n: ts.Node = node;
|
||||
while (ts.isParenthesizedExpression(n)) n = n.expression;
|
||||
const seen = new Set<ts.Symbol>();
|
||||
while (lowerer && ts.isIdentifier(n)) {
|
||||
const symbol = lowerer.resolveValueSymbol(n);
|
||||
if (!symbol || seen.has(symbol)) break;
|
||||
seen.add(symbol);
|
||||
const declaration = lowerer.checker.valueDeclarationOf(symbol);
|
||||
if (declaration && ts.isFunctionDeclaration(declaration)) { n = declaration; break; }
|
||||
if (!declaration || !ts.isVariableDeclaration(declaration) || !declaration.initializer ||
|
||||
(ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const) === 0) break;
|
||||
n = declaration.initializer;
|
||||
while (ts.isParenthesizedExpression(n)) n = n.expression;
|
||||
}
|
||||
if (ts.isIdentifier(n)) return n.text;
|
||||
if (ts.isPropertyAccessExpression(n)) return n.name.text;
|
||||
if ((ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) && n.name) return n.name.text;
|
||||
@@ -1334,7 +1344,7 @@ export class Lowerer {
|
||||
// the initializer cannot fall back to a narrowing adapter.
|
||||
if (ts.isIdentifier(node) && fallback?.kind === "func") {
|
||||
const decl = this.checker.valueDeclarationOf(symbol);
|
||||
if (decl && ts.isVariableDeclaration(decl) && decl.initializer) {
|
||||
if (decl && ts.isVariableDeclaration(decl) && !decl.type && !hasJsTypeAnnotation(decl) && decl.initializer) {
|
||||
let init: ts.Expression = decl.initializer;
|
||||
while (ts.isParenthesizedExpression(init)) init = init.expression;
|
||||
if (ts.isIdentifier(init)) {
|
||||
@@ -1711,6 +1721,7 @@ export class Lowerer {
|
||||
* base vtable signature is collected. Unused checked slots carry undefined. */
|
||||
readonly virtualJsMethodArity = new Map<ts.MethodDeclaration, number>();
|
||||
readonly checkedVirtualJsMethods = new Set<ts.MethodDeclaration>();
|
||||
readonly checkedVirtualJsAccessors = new Set<ts.GetAccessorDeclaration | ts.SetAccessorDeclaration>();
|
||||
readonly checkedVirtualJsValueReturns = new Set<ts.MethodDeclaration>();
|
||||
/** The class whose members are lowering — `super` binds lexically to it
|
||||
* (arrows inside methods lower within this window, so they see it too). */
|
||||
@@ -2712,7 +2723,10 @@ export class Lowerer {
|
||||
const symbol = this.resolveValueSymbol(ident);
|
||||
if (!symbol) return null;
|
||||
const g = this.globalsBySymbol.get(symbol);
|
||||
if (g) return g;
|
||||
if (g) {
|
||||
if (g.type.kind === "func") g.type = this.runtimeOptionalBindingType(ident, g.type);
|
||||
return g;
|
||||
}
|
||||
for (const d of this.checker.declarationsOf(symbol)) {
|
||||
const byDecl = this.globalsByDeclNode.get(d);
|
||||
if (byDecl) return byDecl;
|
||||
@@ -2957,6 +2971,13 @@ export class Lowerer {
|
||||
if (!s) return null;
|
||||
return s.flags & ts.SymbolFlags.Alias ? this.checker.getAliasedSymbol(s) : s;
|
||||
};
|
||||
const signatureOf = (node: ts.Identifier): FnSig | null => {
|
||||
const symbol = symbolOf(node);
|
||||
if (!symbol) return null;
|
||||
const projection = this.staticCallables.get(symbol);
|
||||
return this.fnSigsBySymbol.get(symbol) ??
|
||||
(projection?.kind === "declared-function" ? projection.signature : null);
|
||||
};
|
||||
const functionDeclBySymbol = new Map<ts.Symbol, ts.FunctionLikeDeclaration>();
|
||||
type RuntimeSig = {
|
||||
params: ParamShape[];
|
||||
@@ -3213,6 +3234,16 @@ export class Lowerer {
|
||||
for (const expression of returnsOf(fn)) if (mayBeOptional(expression)) found = true;
|
||||
return found;
|
||||
};
|
||||
const jsLambdas: ts.FunctionLikeDeclaration[] = [];
|
||||
for (const sf of sourceFiles) {
|
||||
if (!isJsSourceFile(sf)) continue;
|
||||
ts.walkPreorder(sf, (node) => {
|
||||
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) jsLambdas.push(node);
|
||||
});
|
||||
}
|
||||
for (const [symbol, signature] of signatureBySymbol) {
|
||||
if (signature.returnType.kind === "union" && this.armTag(signature.returnType.unionId, UNDEFINED_T) >= 0) optionalReturns.add(symbol);
|
||||
}
|
||||
const promoteHofCallback = (callback: ts.Expression, parameterIndices: readonly number[], seen = new Set<ts.Symbol>()): boolean => {
|
||||
let changed = false;
|
||||
const checkerTypeContainsTypeParameter = (type: ts.Type): boolean => {
|
||||
@@ -3607,6 +3638,20 @@ export class Lowerer {
|
||||
changed = false;
|
||||
for (const sf of sourceFiles) if (scanFile(sf)) changed = true;
|
||||
for (const [symbol, decl] of functionDeclBySymbol) if (scanReturns(symbol, decl)) changed = true;
|
||||
for (const fn of jsLambdas) {
|
||||
if (!bodyReturnsOptional(fn)) continue;
|
||||
const mapped = this.mapTypeOf(this.typeOf(fn));
|
||||
if (mapped?.kind !== "func") continue;
|
||||
const previous = this.runtimeOptionalFunctionReturnType(fn, mapped.ret);
|
||||
if (previous.kind === "generator") continue;
|
||||
const next = previous.kind === "promise"
|
||||
? { ...previous, inner: this.runtimeOptionalType(previous.inner) }
|
||||
: this.runtimeOptionalType(previous);
|
||||
if (!typeEquals(previous, next)) {
|
||||
this.runtimeOptionalFunctionReturns.set(fn, next);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
// Every implementation of a virtual slot must retain the same ABI,
|
||||
// including when just one body forwards an omitted parameter.
|
||||
for (const family of optionalMethodFamilies) {
|
||||
@@ -3644,11 +3689,32 @@ export class Lowerer {
|
||||
if (decl) this.runtimeOptionalFunctionReturns.set(decl, arithmetic);
|
||||
}
|
||||
}
|
||||
for (const [declaration, global] of this.globalsByDeclNode) {
|
||||
if (global.type.kind !== "func" || !ts.isBinaryExpression(declaration) || !isJsSourceFile(declaration.getSourceFile())) continue;
|
||||
const value = peel(declaration.right);
|
||||
if (!ts.isFunctionExpression(value) && !ts.isArrowFunction(value)) continue;
|
||||
global.type = { ...global.type, ret: this.runtimeOptionalFunctionReturnType(value, global.type.ret) };
|
||||
}
|
||||
for (const sf of sourceFiles) {
|
||||
ts.walkPreorder(sf, (node) => {
|
||||
if (!ts.isVariableDeclaration(node) || !ts.isIdentifier(node.name)) return;
|
||||
const symbol = symbolOf(node.name);
|
||||
if (!symbol) return;
|
||||
if (isJsSourceFile(sf) && !node.type && !hasJsTypeAnnotation(node) && node.initializer &&
|
||||
(ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
|
||||
const global = this.globalsBySymbol.get(symbol);
|
||||
if (global?.type.kind === "func") global.type = { ...global.type, ret: this.runtimeOptionalFunctionReturnType(node.initializer, global.type.ret) };
|
||||
}
|
||||
if (!node.type && !hasJsTypeAnnotation(node) && node.initializer && ts.isIdentifier(node.initializer)) {
|
||||
const global = this.globalsBySymbol.get(symbol);
|
||||
const current = global?.type ?? this.mapTypeOf(this.typeOf(node.name));
|
||||
const source = current?.kind === "func" ? signatureOf(node.initializer) : null;
|
||||
if (source && current?.kind === "func") {
|
||||
const promoted: IrType = { ...current, params: source.params.filter((param) => param.mode !== "dynRest" && param.mode !== "arguments").map((param) => param.type), ret: source.returnType };
|
||||
this.runtimeOptionalBindingTypes.set(symbol, promoted);
|
||||
if (global) global.type = promoted;
|
||||
}
|
||||
}
|
||||
const fields = optionalFields.get(symbol);
|
||||
if (!dynamicObjectEntryRows.has(symbol) && !optionalSymbols.has(symbol) && !fields) return;
|
||||
const current = this.mapTypeOf(this.typeOf(node.name));
|
||||
@@ -3656,7 +3722,7 @@ export class Lowerer {
|
||||
let promoted = dynamicObjectEntryRows.has(symbol) ? DYN : current;
|
||||
if (!dynamicObjectEntryRows.has(symbol) && optionalSymbols.has(symbol)) promoted = addUndefined(promoted);
|
||||
if (promoted.kind === "func" && node.initializer && ts.isIdentifier(node.initializer)) {
|
||||
const sourceSig = this.fnSigOf(node.initializer);
|
||||
const sourceSig = signatureOf(node.initializer);
|
||||
if (sourceSig) {
|
||||
promoted = {
|
||||
...promoted,
|
||||
@@ -8070,7 +8136,7 @@ export class Lowerer {
|
||||
// NamedEvaluation through a variable initializer) — inspect prints
|
||||
// [Function: name] and call errors spell it, like Node.
|
||||
if (e.kind === "dynFrom" && e.value.type.kind === "func" && e.fnName === undefined) {
|
||||
const name = jsFuncNameOf(node);
|
||||
const name = jsFuncNameOf(node, this);
|
||||
if (name !== null) e = { ...e, fnName: name };
|
||||
}
|
||||
// A union the plain re-tag declined (stranded NON-unit arms): when the
|
||||
|
||||
@@ -26,7 +26,7 @@ export function lowerModuleNamespaceElement(lowerer: Lowerer, expr: ts.ElementAc
|
||||
const target = exported.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(exported) : exported;
|
||||
if (!(target.flags & ts.SymbolFlags.Value)) return;
|
||||
const declaration = lowerer.checker.valueDeclarationOf(target);
|
||||
const identifier = declaration && "name" in declaration ? declaration.name as ts.Node | undefined : undefined;
|
||||
const identifier = declaration?.name as ts.Node | undefined;
|
||||
if (!identifier || !ts.isIdentifier(identifier)) {
|
||||
lowerer.unsupported("SC1090", expr, "computed namespace reads of unnamed exports");
|
||||
}
|
||||
@@ -36,7 +36,7 @@ export function lowerModuleNamespaceElement(lowerer: Lowerer, expr: ts.ElementAc
|
||||
});
|
||||
body.push({ kind: "return", value: dynUndefinedExpr(loc), loc });
|
||||
const fn: IrFunction = { name, params: [{ localId: "namespace", name: "namespace", type }, { localId: "key", name: "key", type: STRING }],
|
||||
returnType: DYN, locals: [{ id: "namespace", name: "namespace", type, mutable: false }, { id: "key", name: "key", type: STRING, mutable: false }, ...context.locals.values()], body, loc };
|
||||
returnType: DYN, locals: [{ id: "namespace", name: "namespace", type, mutable: false }, { id: "key", name: "key", type: STRING, mutable: false }, ...context.locals], body, loc };
|
||||
lowerer.liftedFns.push(fn);
|
||||
} finally {
|
||||
lowerer.fnStack.pop();
|
||||
|
||||
@@ -8,7 +8,7 @@ import { literalValues } from "./literal-values.js";
|
||||
import { remapUnionDiscriminant } from "./union-discriminants.js";
|
||||
|
||||
import { isJsSourceFile, isNodeTypesPath } from "./program.js";
|
||||
import { functionCanFallThrough } from "./function-completion.js";
|
||||
import { functionCanReturnUndefined } from "./function-completion.js";
|
||||
import { accessorSlotProp, recordTextCodecClass } from "../ir/ir.js";
|
||||
// typeKey moved to ir/ir.ts (the backend needs it too, for per-type
|
||||
// helper interning); re-exported here so frontend call sites keep their
|
||||
@@ -2846,7 +2846,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
if (sigDecl && isJsSourceFile(sigDecl.getSourceFile()) && ret?.kind === "array") ret = DYN;
|
||||
if (sigDecl && ts.isFunctionLike(sigDecl) && isJsSourceFile(sigDecl.getSourceFile()) && !sigDecl.type &&
|
||||
!/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && ret?.kind === "record") ret = DYN;
|
||||
if (ret && sigDecl && isJsSourceFile(sigDecl.getSourceFile()) && functionCanFallThrough(sigDecl)) ret = withUndefinedArm(ret, ctx.unions) ?? ret;
|
||||
if (sigDecl && ts.isFunctionLike(sigDecl) && isJsSourceFile(sigDecl.getSourceFile()) && !sigDecl.type &&
|
||||
!/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) &&
|
||||
(ret?.kind === "date" || ret?.kind === "union" && ctx.unions.get(ret.unionId)?.arms.some((arm) => arm.kind === "func"))) ret = DYN;
|
||||
if (ret && sigDecl && isJsSourceFile(sigDecl.getSourceFile()) && functionCanReturnUndefined(sigDecl)) ret = withUndefinedArm(ret, ctx.unions) ?? ret;
|
||||
if (!ret) return null;
|
||||
return typedRest
|
||||
? { kind: "func", params, ret, rest: true, restAbi: "typed" }
|
||||
|
||||
@@ -210,9 +210,9 @@ export function everyExprChild(node: IrExpr, expr: (expr: IrExpr) => boolean, st
|
||||
case "dynFromJsval":
|
||||
return expr(node.value);
|
||||
case "dynCall":
|
||||
return expr(node.callee) && (node.receiver === undefined || expr(node.receiver)) && node.args.every((child) => expr(child)) && (node.calleeNameValue === undefined || expr(node.calleeNameValue));
|
||||
return expr(node.callee) && (node.receiver === undefined || expr(node.receiver)) && (node.calleeNameValue === undefined || expr(node.calleeNameValue)) && node.args.every((child) => expr(child));
|
||||
case "dynInvoke":
|
||||
return expr(node.recv) && node.args.every((child) => expr(child)) && (node.calleeNameValue === undefined || expr(node.calleeNameValue));
|
||||
return expr(node.recv) && (node.calleeNameValue === undefined || expr(node.calleeNameValue)) && node.args.every((child) => expr(child));
|
||||
case "dynArrLit":
|
||||
return node.elems.every((child) => expr(child));
|
||||
case "dynObjLit":
|
||||
@@ -475,13 +475,18 @@ export function mapExprChildren(node: IrExpr, expr: (expr: IrExpr) => IrExpr, st
|
||||
case "dynFromJsval":
|
||||
return { ...node, value: expr(node.value) };
|
||||
case "dynCall": {
|
||||
const callee = expr(node.callee);
|
||||
return { ...node, callee, ...(node.receiver === undefined ? {} : { receiver: expr(node.receiver) }), args: node.args.map((child) => expr(child)),
|
||||
...(node.calleeNameValue === undefined ? {} : { calleeNameValue: expr(node.calleeNameValue) }) };
|
||||
const result: Extract<IrExpr, { kind: "dynCall" }> = { ...node, callee: expr(node.callee) };
|
||||
if (node.receiver !== undefined) result.receiver = expr(node.receiver);
|
||||
if (node.calleeNameValue !== undefined) result.calleeNameValue = expr(node.calleeNameValue);
|
||||
result.args = node.args.map((child) => expr(child));
|
||||
return result;
|
||||
}
|
||||
case "dynInvoke": {
|
||||
const result: Extract<IrExpr, { kind: "dynInvoke" }> = { ...node, recv: expr(node.recv) };
|
||||
if (node.calleeNameValue !== undefined) result.calleeNameValue = expr(node.calleeNameValue);
|
||||
result.args = node.args.map((child) => expr(child));
|
||||
return result;
|
||||
}
|
||||
case "dynInvoke":
|
||||
return { ...node, recv: expr(node.recv), args: node.args.map((child) => expr(child)),
|
||||
...(node.calleeNameValue === undefined ? {} : { calleeNameValue: expr(node.calleeNameValue) }) };
|
||||
case "dynArrLit":
|
||||
return { ...node, elems: node.elems.map((child) => expr(child)) };
|
||||
case "dynObjLit":
|
||||
|
||||
@@ -7,8 +7,6 @@ import { analyze } from "../src/index.js";
|
||||
test.each([
|
||||
["typed listeners", `/** @param {number} value */ function listener(value) { console.log(value); }
|
||||
const ee = new EventEmitter(); ee.on('value', listener); ee.emit('value'); ee.emit('value', 1);`, "conflicting argument types"],
|
||||
["mixed listener parameters", `function listener(first, second = 1) { console.log(first, second); }
|
||||
const ee = new EventEmitter(); ee.on('value', listener); ee.emit('value'); ee.emit('value', 1, 2);`, "conflicting argument types"],
|
||||
["emit overrides", `class Derived extends EventEmitter { emit(event, ...args) { return super.emit(event, ...args); } }
|
||||
const ee = new Derived(); ee.on('value', (value) => console.log(value)); ee.emit('value'); ee.emit('value', 1);`, "variable-arity emit through an EventEmitter subclass overriding emit"],
|
||||
["listener arrays", `const ee = new EventEmitter(); ee.on('value', () => {});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { analyze, compile } from "../src/index.js";
|
||||
|
||||
const sanitize = process.env["SCRIPTC_SAN"] === "1";
|
||||
|
||||
test.each(["llvm"] as const)("incompatible JS return overrides refuse only when called (%s)", async (backend) => {
|
||||
test.each(["llvm"] as const)("JS return overrides preserve calls and argument order (%s)", async (backend) => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "scriptc-override-returns-"));
|
||||
try {
|
||||
const entry = join(dir, "main.cjs");
|
||||
@@ -17,15 +17,15 @@ class Base {
|
||||
dispatch(value) { return this.method(value, undefined); }
|
||||
}
|
||||
class Derived extends Base {
|
||||
// @ts-expect-error Deliberately exercise the native override refusal.
|
||||
method(value, other) { console.log("must not execute", value, other); }
|
||||
// @ts-expect-error JavaScript permits a different return type at runtime.
|
||||
method(value, other) { console.log("body", value, other); }
|
||||
}
|
||||
class Leaf extends Derived {
|
||||
callSuper(value) { super.method(value, undefined); }
|
||||
}
|
||||
class Short extends Base {
|
||||
// @ts-expect-error Deliberately exercise the native override refusal.
|
||||
method(value) { console.log("must not execute short", value); }
|
||||
// @ts-expect-error JavaScript permits a different return type at runtime.
|
||||
method(value) { console.log("short body", value); }
|
||||
}
|
||||
function argument(label) { console.log("argument", label); return label; }
|
||||
const derived = new Derived();
|
||||
@@ -42,23 +42,22 @@ console.log("after");
|
||||
const { coverage } = analyze(entry, { dynamic: false });
|
||||
expect(coverage.diagnostics).toEqual([]);
|
||||
expect(coverage.stats.statementsIsland).toBe(0);
|
||||
expect(coverage.runtimeFences?.map((d) => [d.code, d.message])).toEqual([
|
||||
["SC1090", expect.stringContaining("overriding method 'method' with a different return type")],
|
||||
["SC1090", expect.stringContaining("overriding method 'method' with a different return type")],
|
||||
]);
|
||||
expect(coverage.runtimeFences ?? []).toEqual([]);
|
||||
const result = await compile(entry, { backend, dynamic: false, sanitize, outDir: dir, outPath: join(dir, "program") });
|
||||
expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true);
|
||||
if (!result.ok) return;
|
||||
const child = spawnSync(result.binaryPath, [], { encoding: "utf8" });
|
||||
expect(child.status).toBe(0);
|
||||
expect(child.stderr).toBe("");
|
||||
expect(child.stdout).toBe("argument direct first\nargument direct second\ndirect true true\nargument virtual\nvirtual true\nargument super\nsuper true\nargument short\nshort true\nafter\n");
|
||||
const oracle = spawnSync(process.execPath, [entry], { encoding: "utf8" });
|
||||
expect(oracle.status).toBe(0);
|
||||
expect(child.stdout).toBe(oracle.stdout);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("incompatible JS parameter overrides refuse at method entry", async () => {
|
||||
test("JS parameter overrides preserve calls and argument order", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "scriptc-override-parameters-"));
|
||||
try {
|
||||
const entry = join(dir, "main.cjs");
|
||||
@@ -69,7 +68,7 @@ class Base {
|
||||
dispatch(value) { return this.method(value); }
|
||||
}
|
||||
class Derived extends Base {
|
||||
method(value = "x") { console.log("must not execute", value); return "x"; }
|
||||
method(value = "x") { console.log("body", value); return "x"; }
|
||||
}
|
||||
class Leaf extends Derived { callSuper(value) { super.method(value); } }
|
||||
function argument(label) { console.log("argument", label); return 2; }
|
||||
@@ -84,16 +83,16 @@ console.log("after");
|
||||
`);
|
||||
const { coverage } = analyze(entry, { dynamic: false });
|
||||
expect(coverage.diagnostics).toEqual([]);
|
||||
expect(coverage.runtimeFences?.map((d) => [d.code, d.message])).toEqual([
|
||||
["SC1090", expect.stringContaining("overriding method 'method' with a different signature")],
|
||||
]);
|
||||
expect(coverage.runtimeFences ?? []).toEqual([]);
|
||||
const result = await compile(entry, { backend: "llvm", dynamic: false, sanitize, outDir: dir, outPath: join(dir, "program") });
|
||||
expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true);
|
||||
if (!result.ok) return;
|
||||
const child = spawnSync(result.binaryPath, [], { encoding: "utf8" });
|
||||
expect(child.status).toBe(0);
|
||||
expect(child.stderr).toBe("");
|
||||
expect(child.stdout).toBe("argument direct\ndirect true true\nargument virtual\nvirtual true\nargument super\nsuper true\nafter\n");
|
||||
const oracle = spawnSync(process.execPath, [entry], { encoding: "utf8" });
|
||||
expect(oracle.status).toBe(0);
|
||||
expect(child.stdout).toBe(oracle.stdout);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
@@ -102,15 +101,14 @@ console.log("after");
|
||||
test.each([
|
||||
["async methods", `class Base { method() { return 1; } } class Derived extends Base { async method() { return "x"; } }`],
|
||||
["generators", `class Base { method() { return 1; } } class Derived extends Base { *method() { yield "x"; } }`],
|
||||
["accessors", `class Base { get value() { return 1; } } class Derived extends Base { get value() { return "x"; } }`],
|
||||
])("%s retain their class signature refusal", (_name, source) => {
|
||||
])("%s retain their method entry refusal", (_name, source) => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "scriptc-override-signatures-"));
|
||||
try {
|
||||
const entry = join(dir, "main.cjs");
|
||||
writeFileSync(entry, "// @ts-nocheck\n" + source + "\nnew Derived();\n");
|
||||
writeFileSync(entry, "// @ts-nocheck\n" + source + "\nnew Derived().method();\n");
|
||||
const { coverage } = analyze(entry, { dynamic: false });
|
||||
const diagnostics = [...coverage.diagnostics, ...(coverage.runtimeFences ?? [])];
|
||||
expect(diagnostics.some((d) => d.code === "SC1090" && /overriding.*different (signature|type)/.test(d.message)), JSON.stringify(diagnostics)).toBe(true);
|
||||
expect(diagnostics.some((d) => d.code === "SC1090" && /overriding.*(async|generator|different (signature|type))/.test(d.message)), JSON.stringify(diagnostics)).toBe(true);
|
||||
expect(diagnostics.some((d) => /different return type/.test(d.message))).toBe(false);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
|
||||
@@ -14,8 +14,6 @@ function fixture(source: string) {
|
||||
test.each([
|
||||
"Object.defineProperty(value, 'x', {value: 2, writable: false});",
|
||||
"Object.defineProperties(value, {x: {value: 2}});",
|
||||
"Object.defineProperty(value, 'data', {get() { return this.x; }});",
|
||||
"Object.defineProperties(value, {data: {get() { return this.x; }}});",
|
||||
])("keeps unsafe native descriptor changes fenced: %s", (operation) => {
|
||||
const { dir, entry } = fixture(`class Value { constructor() { this.x = 1; } } const value = new Value(); ${operation}`);
|
||||
try {
|
||||
@@ -26,32 +24,34 @@ test.each([
|
||||
}
|
||||
});
|
||||
|
||||
test("incompatible JS overrides throw at entry without executing the body", async () => {
|
||||
test("JS overrides with different return types match Node", async () => {
|
||||
const { dir, entry } = fixture(`
|
||||
class Base { copy(value = 1) { return value; } }
|
||||
class Child extends Base {
|
||||
/** @returns {string} */
|
||||
// @ts-expect-error Exercise the runtime fence for an incompatible JavaScript override.
|
||||
copy(value) { console.log('must not execute'); return String(value); }
|
||||
// @ts-expect-error JavaScript permits a different return type at runtime.
|
||||
copy(value) { console.log('child'); return String(value); }
|
||||
}
|
||||
const child = new Child();
|
||||
try { child.copy(2); } catch (error) { console.log(error.message.includes("overriding method 'copy' with a different return type")); }
|
||||
console.log(child.copy(2), typeof child.copy(3));
|
||||
console.log(new Base().copy());
|
||||
`);
|
||||
try {
|
||||
const result = await compile(entry, { dynamic: false, outDir: dir, outPath: join(dir, "program"), sanitize: process.env["SCRIPTC_SAN"] === "1" });
|
||||
if (!result.ok) throw new Error(JSON.stringify(result.diagnostics));
|
||||
const run = spawnSync(result.binaryPath, { encoding: "utf8", timeout: 30_000 });
|
||||
const oracle = spawnSync(process.execPath, [entry], { encoding: "utf8", timeout: 30_000 });
|
||||
expect(run.error).toBeUndefined();
|
||||
expect({ status: run.status, signal: run.signal, stdout: run.stdout, stderr: run.stderr }).toEqual({ status: 0, signal: null, stdout: "true\n1\n", stderr: "" });
|
||||
expect(oracle.status).toBe(0);
|
||||
expect({ status: run.status, signal: run.signal, stdout: run.stdout, stderr: run.stderr }).toEqual({ status: oracle.status, signal: oracle.signal, stdout: oracle.stdout, stderr: oracle.stderr });
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("replaced instance constructors refuse before evaluating constructor arguments", async () => {
|
||||
test("replaced instance constructors preserve argument evaluation and match Node", async () => {
|
||||
const { dir, entry } = fixture(`
|
||||
function argument() { console.log('must not execute'); return 2; }
|
||||
function argument() { console.log('argument'); return 2; }
|
||||
class Value {
|
||||
constructor(x = 1) { this.x = x; }
|
||||
clone() { return new this.constructor(argument()); }
|
||||
@@ -59,14 +59,16 @@ class Value {
|
||||
function replace(value) { value.constructor = Value; }
|
||||
const value = new Value();
|
||||
replace(value);
|
||||
try { value.clone(); } catch (error) { console.log(error.message.includes('replaced instance constructor')); }
|
||||
console.log(value.clone().x);
|
||||
`);
|
||||
try {
|
||||
const result = await compile(entry, { dynamic: false, outDir: dir, outPath: join(dir, "program"), sanitize: process.env["SCRIPTC_SAN"] === "1" });
|
||||
if (!result.ok) throw new Error(JSON.stringify(result.diagnostics));
|
||||
const run = spawnSync(result.binaryPath, { encoding: "utf8", timeout: 30_000 });
|
||||
const oracle = spawnSync(process.execPath, [entry], { encoding: "utf8", timeout: 30_000 });
|
||||
expect(run.error).toBeUndefined();
|
||||
expect({ status: run.status, signal: run.signal, stdout: run.stdout, stderr: run.stderr }).toEqual({ status: 0, signal: null, stdout: "true\n", stderr: "" });
|
||||
expect(oracle.status).toBe(0);
|
||||
expect({ status: run.status, signal: run.signal, stdout: run.stdout, stderr: run.stderr }).toEqual({ status: oracle.status, signal: oracle.signal, stdout: oracle.stdout, stderr: oracle.stderr });
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -9772,12 +9772,42 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-abort-portable.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-abort-portable.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-array-constructor.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-array-constructor.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-array-default-buffer.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-array-default-buffer.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-array-iterators.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-array-iterators.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-arrow-bind.cjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-arrow-bind.cjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-callable-proxy.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-callable-proxy.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-chained-native-results.mjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-chained-native-results.mjs"
|
||||
@@ -9808,36 +9838,121 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-cjs-callable-snapshots/main.cjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-cjs-callable-snapshots/factories.cjs",
|
||||
"<repo>/tests/corpus/js-cjs-callable-snapshots/main.cjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-added-parameters.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-added-parameters.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-checked-overrides.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-checked-overrides.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-computed-fields.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-computed-fields.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-enumeration.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-enumeration.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-field-accessor-override.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-field-accessor-override.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-fluent-overrides.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-fluent-overrides.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-late-method-values.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-late-method-values.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-member-dispatch.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-member-dispatch.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-return-shapes.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-return-shapes.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-shadowed-methods.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-shadowed-methods.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-stored-method-receiver.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-stored-method-receiver.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-class-subclass-properties.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-class-subclass-properties.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-computed-prototype-additions.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-computed-prototype-additions.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-constructor-returned-callable.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-constructor-returned-callable.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-constructor-spread.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-constructor-spread.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-dataview-dispatch.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-dataview-dispatch.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-declared-fields.cjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-declared-fields.cjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-default-listener-arguments.cjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-default-listener-arguments.cjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-default-options/main.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-default-options/options.js",
|
||||
@@ -9852,6 +9967,12 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-deferred-registry-effects.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-deferred-registry-effects.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-derived-constructor-hoisting.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-derived-constructor-hoisting.js"
|
||||
@@ -9876,30 +9997,104 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-implicit-call-results.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-implicit-call-results.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-inferred-callback-exports/main.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-inferred-callback-exports/internal.js",
|
||||
"<repo>/tests/corpus/js-inferred-callback-exports/facade.js",
|
||||
"<repo>/tests/corpus/js-inferred-callback-exports/main.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-inferred-class-array-return.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-inferred-class-array-return.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-inferred-object-values.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-inferred-object-values.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-inherited-field-storage.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-inherited-field-storage.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-inherited-parameter-abi.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-inherited-parameter-abi.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-iterable-spread.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-iterable-spread.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-loose-dynamic-primitives.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-loose-dynamic-primitives.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-map-object-seeds.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-map-object-seeds.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-member-increment.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-member-increment.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-module-function-values.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-module-function-values.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-mutable-null-members.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-mutable-null-members.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-nested-native-descriptors.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-nested-native-descriptors.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-normalize-dispatch.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-normalize-dispatch.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-nullish-inherited-fields.mjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-nullish-inherited-fields.mjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-object-receiver-storage.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-object-receiver-storage.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-object-returning-constructors.mjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-object-returning-constructors.mjs"
|
||||
@@ -9924,30 +10119,102 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-optional-member-call.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-optional-member-call.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-primitive-union-addition.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-primitive-union-addition.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-promise-all-values.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-promise-all-values.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-promise-catch-handler.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-promise-catch-handler.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-promise-finally-order.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-promise-finally-order.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-promise-pending-reactions.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-promise-pending-reactions.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-prototype-host-boundary.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-prototype-host-boundary.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-reflect-receivers.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-reflect-receivers.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-regex-record-captures.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-regex-record-captures.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-scalar-iterables.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-scalar-iterables.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-shared-method-dispatch.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-shared-method-dispatch.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-specialized-field-method.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-specialized-field-method.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-static-accessors.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-static-accessors.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-static-method-values.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-static-method-values.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-static-registries.cjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-static-registries.cjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-super-accessor-initialization.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-super-accessor-initialization.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/js-uninitialized-native-fields.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-uninitialized-native-fields.js"
|
||||
@@ -10536,6 +10803,12 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/regex-optional-captures.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/regex-optional-captures.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/regex-optional-templates.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/regex-optional-templates.ts"
|
||||
@@ -10663,6 +10936,12 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/string-normalize.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/string-normalize.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/string-pattern-replacement.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/string-pattern-replacement.ts"
|
||||
@@ -10909,6 +11188,12 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/unknown-array-holes.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/unknown-array-holes.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/unknown-collections.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/unknown-collections.ts"
|
||||
@@ -11763,12 +12048,6 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/diagnostics/js-extends-poisoned-base.js": {
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/js-extends-poisoned-base.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/diagnostics/jsdoc-import-require.js": {
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/jsdoc-import-require.js"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
|
||||
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "wasi", extraRuntimeSources: ["scr_abort.c"], runtimeDefines: ["_GNU_SOURCE", "_WASI_EMULATED_SIGNAL", "_WASI_EMULATED_PROCESS_CLOCKS"], omitRuntimeSources: ["scr_child.c"], omitOptionalSources: ["scr_dyn_handle.c", "scr_loop_kqueue.c", "scr_loop_epoll.c", "scr_loop_wsapoll.c", "scr_net.c", "scr_http.c", "scr_http2.c", "scr_dgram.c", "scr_watch.c", "scr_tls_ca.c", "scr_tls.c", "scr_fetch.c", "scr_net_island.c"], omitArchives: ["mbedtls"], compiler: "zig", compilerArgs: ["cc"], archiver: "zig", archiverArgs: ["ar"], target: { name: "wasm32-wasi", llvm_triple: "wasm32-unknown-wasi", architecture: "wasm32", object_format: "wasm", minimum_os: "WASI Preview 1" }, targetArgs: ["-target", "wasm32-wasi"], compileFlags: [], systemLibraries: [{ name: "wasi-emulated-signal", predicate: true }, { name: "wasi-emulated-process-clocks", predicate: true }] });
|
||||
process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "wasi", extraRuntimeSources: ["scr_abort.c"], runtimeDefines: ["_GNU_SOURCE", "_WASI_EMULATED_SIGNAL", "_WASI_EMULATED_PROCESS_CLOCKS"], omitRuntimeSources: ["scr_child.c"], omitOptionalSources: ["scr_loop_kqueue.c", "scr_loop_epoll.c", "scr_loop_wsapoll.c", "scr_net.c", "scr_http.c", "scr_http2.c", "scr_dgram.c", "scr_watch.c", "scr_tls_ca.c", "scr_tls.c", "scr_fetch.c", "scr_net_island.c"], omitArchives: ["mbedtls"], compiler: "zig", compilerArgs: ["cc"], archiver: "zig", archiverArgs: ["ar"], target: { name: "wasm32-wasi", llvm_triple: "wasm32-unknown-wasi", architecture: "wasm32", object_format: "wasm", minimum_os: "WASI Preview 1" }, targetArgs: ["-target", "wasm32-wasi"], compileFlags: [], systemLibraries: [{ name: "wasi-emulated-signal", predicate: true }, { name: "wasi-emulated-process-clocks", predicate: true }] });
|
||||
await import("../../runtime-pack-common/scripts/build.mjs");
|
||||
|
||||
@@ -455,22 +455,25 @@ static void scr_dyn_then_entry(ScrFiber *self, void *ap) {
|
||||
/* The handler threw: dst rejects with that. */
|
||||
scr_promise_reject_pending(a->dst);
|
||||
} else if (a->onfin != NULL) {
|
||||
if (r->kind == SCR_DYN_PROMISE) {
|
||||
ScrDynFinallyPack *pack = malloc(sizeof *pack);
|
||||
if (!pack) scr_ad_oom();
|
||||
pack->src = scr_promise_retain(a->src);
|
||||
pack->dst = scr_promise_retain(a->dst);
|
||||
pack->result = r;
|
||||
ScrPromise *waiter = scr_async_spawn_after(r->v.promise, scr_dyn_finally_entry, pack);
|
||||
scr_promise_release(waiter);
|
||||
} else if (rejected) {
|
||||
scr_dyn_release(r);
|
||||
scr_rethrow(c);
|
||||
scr_promise_reject_pending(a->dst);
|
||||
} else {
|
||||
scr_dyn_release(r);
|
||||
scr_promise_fulfill_ref(a->dst, scr_dyn_retain(v), scr_dyn_retain_v, scr_dyn_release_v, NULL);
|
||||
/* finally returns PromiseResolve(result).then(valueThunk), which
|
||||
* the outer reaction adopts. Even an undefined result takes those
|
||||
* jobs before propagating the original fulfillment or rejection. */
|
||||
if (r->kind != SCR_DYN_PROMISE) {
|
||||
ScrPromise *result = scr_promise_new();
|
||||
scr_promise_fulfill_ref(result, r, scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v);
|
||||
r = scr_dyn_new_promise(result);
|
||||
scr_promise_release(result);
|
||||
}
|
||||
ScrPromise *passthrough = scr_promise_new();
|
||||
ScrDynFinallyPack *pack = malloc(sizeof *pack);
|
||||
if (!pack) scr_ad_oom();
|
||||
pack->src = scr_promise_retain(a->src);
|
||||
pack->dst = scr_promise_retain(passthrough);
|
||||
pack->result = r;
|
||||
ScrPromise *waiter = scr_async_spawn_after(r->v.promise, scr_dyn_finally_entry, pack);
|
||||
scr_promise_release(waiter);
|
||||
scr_promise_resolve_dyn(a->dst, scr_dyn_new_promise(passthrough));
|
||||
scr_promise_release(passthrough);
|
||||
} else {
|
||||
scr_promise_resolve_dyn(a->dst, r);
|
||||
}
|
||||
|
||||
@@ -960,7 +960,7 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
scr_str_release(name);
|
||||
scr_dyn_release(pack);
|
||||
return result;
|
||||
} else if (dyn_arr_proto_mutates(method) && !dyn_name_is(method, "fill") && !dyn_name_is(method, "push") && !dyn_name_is(method, "pop")) {
|
||||
} else if (dyn_arr_proto_mutates(method) && !dyn_name_is(method, "fill") && !dyn_name_is(method, "push") && !dyn_name_is(method, "pop") && !dyn_name_is(method, "shift")) {
|
||||
static const char message[] = "This checked array mutation over holes is not supported yet";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return NULL;
|
||||
@@ -1052,6 +1052,7 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
}
|
||||
ScrDyn *first = recv->v.arr.items[0];
|
||||
memmove(recv->v.arr.items, recv->v.arr.items + 1, (len - 1) * sizeof(ScrDyn *));
|
||||
if (recv->v.arr.presence) memmove(recv->v.arr.presence, recv->v.arr.presence + 1, len - 1);
|
||||
recv->v.arr.len = len - 1;
|
||||
return first; /* ownership moves out */
|
||||
}
|
||||
|
||||
@@ -3351,6 +3351,15 @@ ScrDyn *scr_dyn_symbol_key_get(const ScrDyn *value, const ScrDyn *key, bool opti
|
||||
return out;
|
||||
}
|
||||
|
||||
/* Native field tables retain presence and attributes without owning a second
|
||||
* copy of the field's value. This internal update bypasses writable flags. */
|
||||
void scr_dyn_symbol_key_placeholder(ScrDyn *value, ScrDyn *key) {
|
||||
ScrDynEntry *entry = scr_dyn_symbol_entry(value, key, true);
|
||||
if (!entry || entry->accessor) return;
|
||||
scr_dyn_release(entry->value);
|
||||
entry->value = scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored) {
|
||||
ScrDyn *receiver = scr_dyn_symbol_receiver(value);
|
||||
if (!receiver) {
|
||||
@@ -7587,6 +7596,19 @@ ScrDyn *scr_dyn_copy_property_descriptors(ScrDyn *target, const ScrDyn *src) {
|
||||
if (scr_exc_pending()) break;
|
||||
}
|
||||
scr_dyn_release(keys);
|
||||
if (!scr_exc_pending()) {
|
||||
ScrDyn *symbols = scr_dyn_get_own_property_symbols(src);
|
||||
if (!symbols) return NULL;
|
||||
for (size_t i = 0; i < symbols->v.arr.len; i++) {
|
||||
ScrDyn *key = symbols->v.arr.items[i];
|
||||
ScrDyn *descriptor = scr_dyn_get_own_property_descriptor((ScrDyn *)src, key);
|
||||
ScrDyn *result = descriptor ? scr_dyn_define_property(target, key, descriptor) : NULL;
|
||||
scr_dyn_release(descriptor);
|
||||
scr_dyn_release(result);
|
||||
if (scr_exc_pending()) break;
|
||||
}
|
||||
scr_dyn_release(symbols);
|
||||
}
|
||||
return scr_exc_pending() ? NULL : scr_dyn_retain(target);
|
||||
}
|
||||
|
||||
|
||||
@@ -460,6 +460,12 @@ static void qs_stringify_value(QsBuf *b, const ScrDyn *v) {
|
||||
|
||||
ScrStr *scr_qs_stringify(const ScrDyn *obj, const ScrStr *sep,
|
||||
const ScrStr *eq) {
|
||||
if (obj && obj->kind == SCR_DYN_TYPED_REF) {
|
||||
ScrDyn *view = scr_dyn_typed_ref_materialize(obj);
|
||||
ScrStr *result = view && !scr_exc_pending() ? scr_qs_stringify(view, sep, eq) : NULL;
|
||||
scr_dyn_release(view);
|
||||
return result;
|
||||
}
|
||||
const char *sep_b = (sep && sep->len) ? sep->data : "&";
|
||||
size_t sep_len = (sep && sep->len) ? sep->len : 1;
|
||||
const char *eq_b = (eq && eq->len) ? eq->data : "=";
|
||||
@@ -475,6 +481,9 @@ ScrStr *scr_qs_stringify(const ScrDyn *obj, const ScrStr *sep,
|
||||
const ScrStr *k = kd->v.str;
|
||||
const ScrDyn *v = scr_dyn_obj_get(obj, k->data, k->len);
|
||||
if (!v) continue; /* unreachable: keys came from the object */
|
||||
ScrDyn *view = v->kind == SCR_DYN_TYPED_REF ? scr_dyn_typed_ref_materialize(v) : NULL;
|
||||
if (view) v = view;
|
||||
if (scr_exc_pending()) { scr_dyn_release(view); scr_dyn_release(keys); free(fields.data); return NULL; }
|
||||
ScrStr *ks = scr_str_encode_uri_component((ScrStr *)k);
|
||||
if (v->kind == SCR_DYN_ARR) {
|
||||
for (size_t j = 0; j < v->v.arr.len; j++) {
|
||||
@@ -490,6 +499,7 @@ ScrStr *scr_qs_stringify(const ScrDyn *obj, const ScrStr *sep,
|
||||
qs_stringify_value(&fields, v);
|
||||
}
|
||||
scr_str_release(ks);
|
||||
scr_dyn_release(view);
|
||||
}
|
||||
scr_dyn_release(keys);
|
||||
return qb_take(&fields);
|
||||
|
||||
@@ -3789,6 +3789,7 @@ ScrSym *scr_sym_well_known(ScrStr *name);
|
||||
ScrDyn *scr_dyn_symbol_get(const ScrDyn *value, const ScrStr *key);
|
||||
ScrDyn *scr_dyn_symbol_key_get(const ScrDyn *value, const ScrDyn *key, bool optional);
|
||||
void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored);
|
||||
void scr_dyn_symbol_key_placeholder(ScrDyn *value, ScrDyn *key);
|
||||
bool scr_dyn_has_key_computed(const ScrDyn *value, const ScrDyn *key);
|
||||
bool scr_dyn_has_own_computed(const ScrDyn *value, const ScrDyn *key);
|
||||
bool scr_dyn_property_is_enumerable_computed(const ScrDyn *value, const ScrDyn *key);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
let create = (value = 1) => value + 10;
|
||||
exports.create = create;
|
||||
create = (value = 2) => value + 20;
|
||||
exports.current = () => create();
|
||||
const stable = () => 30;
|
||||
exports.stable = stable;
|
||||
exports.alias = stable;
|
||||
@@ -0,0 +1,4 @@
|
||||
const factories = require('./factories.cjs');
|
||||
console.log(factories.create(), factories.create(3));
|
||||
console.log(factories.current(), factories.stable());
|
||||
console.log(factories.alias === factories.stable);
|
||||
@@ -0,0 +1,25 @@
|
||||
class Base {
|
||||
constructor(value = 1) { this.x = value; }
|
||||
copy(value = 1) { return value; }
|
||||
clone(value) { return new this.constructor(value); }
|
||||
}
|
||||
class Child extends Base {
|
||||
/** @returns {string} */
|
||||
// @ts-expect-error JavaScript permits a different return type at runtime.
|
||||
copy(value) { console.log('child'); return String(value); }
|
||||
}
|
||||
const child = new Child(3);
|
||||
console.log(child.copy(2), typeof child.copy(4), new Base().copy());
|
||||
Object.defineProperty(child, 'data', { get() { return this.x; }, configurable: true, enumerable: true });
|
||||
Object.defineProperties(child, { doubled: { get() { return this.x * 2; }, configurable: true } });
|
||||
console.log(child.data, child.doubled, Object.keys(child).join(','));
|
||||
child.x = 5;
|
||||
console.log(child.data, child.doubled, Object.getOwnPropertyDescriptor(child, 'data').enumerable);
|
||||
function replace(value, constructor) { value.constructor = constructor; }
|
||||
function argument() { console.log('argument'); return 7; }
|
||||
replace(child, Base);
|
||||
const first = child.clone(argument());
|
||||
console.log(first.x, first instanceof Base, first instanceof Child);
|
||||
replace(child, Child);
|
||||
const second = child.clone(argument());
|
||||
console.log(second.x, second instanceof Base, second instanceof Child);
|
||||
@@ -0,0 +1,36 @@
|
||||
// JavaScript virtual calls and inherited accessors retain their runtime return shapes.
|
||||
// @ts-nocheck
|
||||
class Base {
|
||||
method(value = 1) { return value; }
|
||||
dispatch(value) { return this.method(value); }
|
||||
get value() { return 1; }
|
||||
read() { return this.value; }
|
||||
}
|
||||
class Text extends Base {
|
||||
method(value = "default") { return String(value); }
|
||||
get value() { return "text"; }
|
||||
}
|
||||
class Empty extends Base {
|
||||
method() {}
|
||||
get value() { return undefined; }
|
||||
}
|
||||
class RecordValue extends Base {
|
||||
method(value) { return { value }; }
|
||||
get value() { return { count: 3 }; }
|
||||
}
|
||||
function wrapper(value, argument) { return value.dispatch(argument); }
|
||||
function accessor(value) { return value.read(); }
|
||||
console.log(wrapper(new Base(), 2), wrapper(new Text(), 3), wrapper(new Empty(), 4));
|
||||
console.log(JSON.stringify(wrapper(new RecordValue(), "four")));
|
||||
console.log(accessor(new Base()), accessor(new Text()), accessor(new Empty()));
|
||||
console.log(JSON.stringify(accessor(new RecordValue())));
|
||||
|
||||
// A helper can select a deferred constructor callback or its final instance.
|
||||
class NodeValue {
|
||||
constructor(value) { this.value = value; }
|
||||
}
|
||||
function nodeOrFactory(deferred, value) {
|
||||
return deferred ? child => new NodeValue(child) : new NodeValue(value);
|
||||
}
|
||||
const factory = nodeOrFactory(true, 0);
|
||||
console.log(factory(7).value, nodeOrFactory(false, 8).value);
|
||||
@@ -0,0 +1,32 @@
|
||||
class C {
|
||||
constructor() {
|
||||
this.foo = () => {
|
||||
console.log("called arrow");
|
||||
};
|
||||
}
|
||||
foo() {
|
||||
console.log("called method");
|
||||
}
|
||||
}
|
||||
|
||||
class D extends C {
|
||||
foo() {
|
||||
console.log("SUPER:");
|
||||
super.foo();
|
||||
console.log("THIS:");
|
||||
this.foo();
|
||||
}
|
||||
}
|
||||
|
||||
const obj = new D();
|
||||
obj.foo();
|
||||
D.prototype.foo.call(obj);
|
||||
|
||||
class NonCallable {
|
||||
constructor() { this.m = 'not callable'; }
|
||||
m() { console.log('unreached'); }
|
||||
}
|
||||
try { new NonCallable().m(); } catch (error) { console.log(error.name, error.message.includes('is not a function')); }
|
||||
class Clean { foo() { return 4; } }
|
||||
class Callable extends Clean { constructor() { super(); this.foo = () => 3; } }
|
||||
console.log(new Callable().foo(), Clean.prototype.foo.call(new Callable()));
|
||||
@@ -0,0 +1,8 @@
|
||||
const { EventEmitter } = require('node:events');
|
||||
function listener(first, second = 1) { console.log(first, second); }
|
||||
const ee = new EventEmitter();
|
||||
ee.on('value', listener);
|
||||
ee.emit('value');
|
||||
ee.emit('value', 1, 2);
|
||||
ee.emit('value', undefined, undefined);
|
||||
ee.emit('value', 'text', null);
|
||||
@@ -0,0 +1,2 @@
|
||||
import * as Internal from "./internal.js";
|
||||
export const toDate = Internal.toDate;
|
||||
@@ -0,0 +1,4 @@
|
||||
export const toDate = value => new Date(value);
|
||||
export default Object.assign(function(value) { return value + 1; }, {
|
||||
fromState(value) { return value - 1; },
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as Facade from "./facade.js";
|
||||
import decorated from "./internal.js";
|
||||
console.log(decorated(4), decorated.fromState(4));
|
||||
function transform(callback) { return { callback }; }
|
||||
const getter = transform(Facade.toDate);
|
||||
const date = getter.callback(0);
|
||||
console.log(date instanceof Date, date.toISOString());
|
||||
|
||||
/** @param {string} value @return {number} */
|
||||
function parse(value) {
|
||||
if (value === "invalid") return;
|
||||
switch (value) {
|
||||
case "two": return 2;
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
/** @param {string} value @return {number|string} */
|
||||
const format = function(value) { return value === "text" ? "value" : parse(value); };
|
||||
console.log(format("invalid"), format("unknown"), format("two"), format("text"));
|
||||
const asyncFormat = async value => parse(value);
|
||||
async function main() {
|
||||
console.log(await asyncFormat("invalid"), await asyncFormat("two"));
|
||||
}
|
||||
main();
|
||||
@@ -0,0 +1,20 @@
|
||||
function call(options) {
|
||||
return options?.render?.(4);
|
||||
}
|
||||
const host = { value: 3, render(n) { return this.value + n; } };
|
||||
console.log(call(host), call({}), call(undefined), call(null));
|
||||
|
||||
function reference(key, options) {
|
||||
function Service() {}
|
||||
Object.setPrototypeOf(Service, { useSync(fn) { return fn(this.key); } });
|
||||
const init = (name, config) => {
|
||||
Service.key = name;
|
||||
Service.render = config?.render;
|
||||
return Service;
|
||||
};
|
||||
return arguments.length ? init(key, options) : init;
|
||||
}
|
||||
const service = reference("scene", host);
|
||||
console.log(service.useSync(name => name.toUpperCase()));
|
||||
console.log(service.render?.(5));
|
||||
console.log(reference()("mesh", {}).useSync(name => name));
|
||||
@@ -0,0 +1,12 @@
|
||||
function id(value) { return value; }
|
||||
async function success() { return 7; }
|
||||
async function failure() { throw new Error("initial"); }
|
||||
|
||||
const events = [];
|
||||
id(success()).finally(() => { events.push("finally"); }).then(value => events.push("value " + value));
|
||||
success().finally(() => { events.push("typed finally"); }).then(value => events.push("typed value " + value));
|
||||
id(success()).finally(() => id(success())).then(value => events.push("adopted " + value));
|
||||
id(failure()).finally(() => id(success())).catch(error => events.push(String(error)));
|
||||
id(success()).finally(() => id(failure())).catch(error => events.push("replacement " + String(error)));
|
||||
queueMicrotask(() => events.push("microtask"));
|
||||
setTimeout(() => console.log(events.join("|")), 0);
|
||||
@@ -1,8 +1,5 @@
|
||||
// Mixes every blocker kind the report distinguishes: SC2011 (operations
|
||||
// on any-typed values past the honest static subset — the binding itself
|
||||
// compiles now), SC2012 (island-backed APIs), SC2010 (__island_eval) —
|
||||
// all of which RUN under --dynamic — and static rejections that no flag
|
||||
// fixes.
|
||||
// Native checked values and loose equality share this program with
|
||||
// island-backed APIs (SC2012) and explicit island evaluation (SC2010).
|
||||
const v: any = 21;
|
||||
const doubled = v * 2;
|
||||
const root = Math.cbrt(81);
|
||||
@@ -10,5 +7,5 @@ const up = (19.99).toPrecision(3);
|
||||
const parsed = Number.parseFloat("1.5"); // the global's string form is static now; the Number static keeps the island
|
||||
const raw = __island_eval("6 * 7");
|
||||
const unknownScore: unknown = 81;
|
||||
const flags = unknownScore == 81; // unknown/object coercion keeps the fence; static primitive pairs lower
|
||||
const flags = unknownScore == 81;
|
||||
console.log("done");
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// The JS INFERENCE-GAP fixture: noImplicitAny is off (this directory's
|
||||
// tsconfig), so the untyped parameter types `any` — landing exactly where
|
||||
// `any` lands in TS: SC2011 (runs with --dynamic) in a static analysis,
|
||||
// compiled-dynamically under --dynamic. JSDoc-typed code right next to it
|
||||
// stays fully static.
|
||||
// noImplicitAny is off in this directory. Inferred JavaScript and the
|
||||
// neighboring JSDoc-typed function both compile as native code, including
|
||||
// when the optional island engine is enabled.
|
||||
'use strict';
|
||||
|
||||
function untyped(x) {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// A JS class EXTENDING a base whose own collection fenced reports at
|
||||
// COMPILE time: the derived statement evaluates its heritage at module
|
||||
// init (top-level declarations always execute), and the base's fence is
|
||||
// the compiler's own — Node defines the base fine and runs on — so a
|
||||
// deferred trap there would be a manufactured divergence, not the
|
||||
// Node-parity JS deferral is licensed by (classFieldSuperAccessibleJs2:
|
||||
// the deferred binary printed nothing where Node prints five lines).
|
||||
// The report names the BASE's real blocker, not the extends edge. Leaf and
|
||||
// derived-only poisoned classes stay deferred (errors.test's shadowing pins).
|
||||
class C {
|
||||
constructor() {
|
||||
this.foo = () => {
|
||||
console.log("called arrow");
|
||||
};
|
||||
}
|
||||
foo() {
|
||||
console.log("called method");
|
||||
}
|
||||
}
|
||||
|
||||
class D extends C {
|
||||
foo() {
|
||||
console.log("SUPER:");
|
||||
super.foo();
|
||||
console.log("THIS:");
|
||||
this.foo();
|
||||
}
|
||||
}
|
||||
|
||||
const obj = new D();
|
||||
obj.foo();
|
||||
D.prototype.foo.call(obj);
|
||||
Vendored
-4
@@ -4,10 +4,6 @@
|
||||
// and the string direction (parse), the latter unblocked by aliased-typeof
|
||||
// narrowing (`var type = typeof val` — the checker only narrows const
|
||||
// aliases), static regex exec, parseFloat, isFinite, and Math.abs/round.
|
||||
// The one remaining fence is parse()'s bare `return;` against its JSDoc
|
||||
// `@return {Number}` claim — the garbage-input path, where Node answers
|
||||
// undefined: a representation the declared type cannot hold, fenced
|
||||
// honestly (ms("not a duration") traps instead of answering undefined).
|
||||
import ms from "ms";
|
||||
|
||||
// The string direction (parse).
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import ms from "ms";
|
||||
|
||||
console.log(ms("not a duration"));
|
||||
console.log(ms("1".repeat(101)));
|
||||
console.log(ms("2 hours"));
|
||||
@@ -16,27 +16,6 @@ class-values.ts:22:11 - error SC2020: 'Error' is part of the standard library ty
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
class-values.ts:31:33 - error SC1090: structurally-typed class-value flows ('typeof Unrelated' into a 'typeof Base' slot: only a class and its subclasses share a slot — extend the base class) are not supported yet
|
||||
|
||||
30 | // is the IR's only class subtyping.
|
||||
31 | const structural: typeof Base = Unrelated;
|
||||
| ^~~~~~~~~
|
||||
32 |
|
||||
|
||||
class-values.ts:35:30 - error SC1090: class values whose constructor signatures differ ('typeof Derived' into a 'typeof Base' slot: construction through the slot completes against the base signature, which 'Derived' does not share — declare matching constructor parameters) is not supported yet
|
||||
|
||||
34 | // would dispatch a mismatched signature.
|
||||
35 | const widened: typeof Base = Derived;
|
||||
| ^~~~~~~
|
||||
36 |
|
||||
|
||||
class-values.ts:40:1 - error SC1090: assigning the inherited static 'tag' through a subclass name (JS creates an OWN property on the subclass — assign through 'Base' instead) is not supported yet
|
||||
|
||||
39 | class PlainSub extends Base {}
|
||||
40 | PlainSub.tag = "write";
|
||||
| ^~~~~~~~~~~~
|
||||
41 |
|
||||
|
||||
class-values.ts:44:1 - error SC1090: assigning the static 'tag' through a class value (JS creates an OWN property on the runtime class — assign through the declaring class's name, which subclasses here) is not supported yet
|
||||
|
||||
43 | let someClass: typeof Base = Base;
|
||||
@@ -65,13 +44,6 @@ class-values.ts:59:18 - error SC1090: class expressions with static initializers
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
60 |
|
||||
|
||||
class-values.ts:69:16 - error SC1090: constructing through a union of class values (annotate the slot with the common constructor type — e.g. `new () => Base` — or narrow first) are not supported yet
|
||||
|
||||
68 | let pick: typeof Left | typeof Right = Left;
|
||||
69 | const picked = new pick();
|
||||
| ^~~~~~~~~~
|
||||
70 | void picked;
|
||||
|
||||
class-values.ts:79:56 - error SC1090: receiver-dependent static methods through a polymorphic class value is not supported yet
|
||||
|
||||
78 | class OtherThis extends UsesThis {}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
scriptc coverage tests/coverage-fixtures/dynamic-mix.ts
|
||||
|
||||
statements analyzed 9
|
||||
compile statically 4 (44%)
|
||||
compile statically 5 (55%)
|
||||
compile dynamically 4 (44%) (island sites — the embedded engine runs them)
|
||||
|
||||
blockers:
|
||||
×1 loose equality (== and !=) SC1040
|
||||
builds with --dynamic — no remaining blockers (the island sites above run in the embedded engine).
|
||||
@@ -1,12 +1,9 @@
|
||||
scriptc coverage tests/coverage-fixtures/dynamic-mix.ts
|
||||
|
||||
statements analyzed 9
|
||||
compile statically 5 (55%)
|
||||
compile statically 6 (66%)
|
||||
|
||||
runs with --dynamic 3 sites (embeds a JS engine, ~620KB — static stays the default)
|
||||
×1 '__island_eval' requires the embedded dynamic engine, which this build does not include SC2010
|
||||
×1 '.toPrecision()' on numbers runs in the embedded dynamic engine, which this build does not include SC2012
|
||||
×1 'Number.parseFloat' runs in the embedded dynamic engine, which this build does not include SC2012
|
||||
|
||||
blockers:
|
||||
×1 loose equality (== and !=) SC1040
|
||||
@@ -1,7 +1,7 @@
|
||||
scriptc coverage tests/coverage-fixtures/js-gap/gap.js
|
||||
|
||||
statements analyzed 5
|
||||
compile statically 4 (80%)
|
||||
compile dynamically 1 (20%) (island sites — the embedded engine runs them)
|
||||
compile statically 5 (100%)
|
||||
compile dynamically 0 (0%) (island sites — the embedded engine runs them)
|
||||
|
||||
builds with --dynamic — no remaining blockers (the island sites above run in the embedded engine).
|
||||
fully static — this program has no dynamic remainder.
|
||||
@@ -1,8 +1,7 @@
|
||||
scriptc coverage tests/coverage-fixtures/mixed.ts
|
||||
|
||||
statements analyzed 13
|
||||
compile statically 11 (84%)
|
||||
compile statically 12 (92%)
|
||||
|
||||
blockers:
|
||||
×1 object destructuring of non-record values (the source is array-typed) SC1031
|
||||
×1 loose equality (== and !=) SC1040
|
||||
×1 object destructuring of non-record values (the source is array-typed) SC1031
|
||||
@@ -46,9 +46,11 @@ date-union-fences.ts:21:18 - error SC1090: constructing through a class value wh
|
||||
| ^~~~~~~~~~~~~~~~~~
|
||||
22 | deferred.initialize();
|
||||
|
||||
date-union-fences.ts:22:1 - error SC1090: method calls like 'deferred.initialize' is not supported yet
|
||||
date-union-fences.ts:22:1 - error SC2004: uses of 'deferred' inherit the blocker on its declaration
|
||||
|
||||
21 | const deferred = new DeferredDate();
|
||||
22 | deferred.initialize();
|
||||
| ^~~~~~~~~~~~~~~~~~~~~
|
||||
23 |
|
||||
| ^~~~~~~~
|
||||
23 |
|
||||
|
||||
hint: the declaration of 'deferred' did not compile — fix the diagnostic reported there and these sites clear with it
|
||||
@@ -34,13 +34,15 @@ intl.ts:13:7 - error SC2020: 'NumberFormat' is part of the standard library type
|
||||
|
||||
hint: Intl formatter values have no representation — the COMPOSED en-US forms lower: new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options; the rest is ICU locale data the binary does not carry
|
||||
|
||||
intl.ts:13:17 - error SC1090: constructing values other than classes declared in the program is not supported yet
|
||||
intl.ts:13:17 - error SC2020: 'new Intl.NumberFormat' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
12 | const withOptions = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format(1234.5);
|
||||
13 | const hoisted = new Intl.NumberFormat("en-US");
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
14 | const viaValue = hoisted.format(1234.5);
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
intl.ts:14:18 - error SC2020: 'NumberFormat.format' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
13 | const hoisted = new Intl.NumberFormat("en-US");
|
||||
@@ -77,32 +79,32 @@ intl.ts:19:17 - error SC2020: 'Number.prototype.toLocaleString with an options b
|
||||
|
||||
hint: the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction digits, grouping) — x.toLocaleString("en-US")
|
||||
|
||||
intl.ts:22:14 - error SC2020: 'DateTimeFormat.format' is part of the standard library types but has no scriptc lowering yet
|
||||
intl.ts:22:14 - error SC2020: 'new Intl.DateTimeFormat' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
21 | // The rest of Intl keeps the ICU story.
|
||||
22 | const when = new Intl.DateTimeFormat("en-US").format();
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
23 | const dur = new Intl.DurationFormat("en-US").format({ hours: 1, minutes: 23 });
|
||||
|
||||
hint: locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data) — what lowers: the composed new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options, plus default Unicode grapheme segmentation with Intl.Segmenter; locale negotiation, word/sentence segmentation, and resolvedOptions remain unsupported
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
intl.ts:23:13 - error SC2020: 'DurationFormat.format' is part of the standard library types but has no scriptc lowering yet
|
||||
intl.ts:23:13 - error SC2020: 'new Intl.DurationFormat' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
22 | const when = new Intl.DateTimeFormat("en-US").format();
|
||||
23 | const dur = new Intl.DurationFormat("en-US").format({ hours: 1, minutes: 23 });
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
24 | const rule = new Intl.PluralRules("en-US").select(1);
|
||||
|
||||
hint: locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data) — what lowers: the composed new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options, plus default Unicode grapheme segmentation with Intl.Segmenter; locale negotiation, word/sentence segmentation, and resolvedOptions remain unsupported
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
intl.ts:24:14 - error SC2020: 'PluralRules.select' is part of the standard library types but has no scriptc lowering yet
|
||||
intl.ts:24:14 - error SC2020: 'new Intl.PluralRules' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
23 | const dur = new Intl.DurationFormat("en-US").format({ hours: 1, minutes: 23 });
|
||||
24 | const rule = new Intl.PluralRules("en-US").select(1);
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
25 | const canon = Intl.getCanonicalLocales("en-us");
|
||||
|
||||
hint: locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data) — what lowers: the composed new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options, plus default Unicode grapheme segmentation with Intl.Segmenter; locale negotiation, word/sentence segmentation, and resolvedOptions remain unsupported
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
intl.ts:25:15 - error SC2020: 'Intl.getCanonicalLocales' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
js-extends-poisoned-base.js:12:5 - error SC1090: constructor-assigned fields shadowing methods are not supported yet
|
||||
|
||||
11 | constructor() {
|
||||
12 | this.foo = () => {
|
||||
| ^~~~~~~~
|
||||
13 | console.log("called arrow");
|
||||
@@ -5,13 +5,4 @@ multiple-errors.ts:8:13 - error SC1042: logical operators on mixed operand types
|
||||
| ^~~~~~~~~~~~
|
||||
9 | while (true) {
|
||||
|
||||
hint: give both operands the same type (number, string, or boolean)
|
||||
|
||||
multiple-errors.ts:13:13 - error SC1040: loose equality (== and !=) is not supported yet
|
||||
|
||||
12 | const anyone: unknown = 1;
|
||||
13 | console.log(anyone == 1);
|
||||
| ^~~~~~~~~~~
|
||||
14 |
|
||||
|
||||
hint: object-to-primitive loose equality can call user-defined valueOf/toString methods — compare explicit primitive conversions instead
|
||||
hint: give both operands the same type (number, string, or boolean)
|
||||
@@ -18,7 +18,7 @@ new-target-field-initializer.ts:9:20 - error SC2004: uses of 'a' inherit the blo
|
||||
|
||||
8 | const a = new A();
|
||||
9 | console.log(typeof a.d);
|
||||
| ^~~
|
||||
| ^
|
||||
10 |
|
||||
|
||||
hint: the declaration of 'a' did not compile — fix the diagnostic reported there and these sites clear with it
|
||||
@@ -12,13 +12,15 @@ private-members.ts:21:12 - error SC1090: constructing through a class value whos
|
||||
| ^~~~~~~~~~~~~~~~~~
|
||||
22 | console.log(rs.read());
|
||||
|
||||
private-members.ts:22:13 - error SC1090: method calls like 'rs.read' is not supported yet
|
||||
private-members.ts:22:13 - error SC2004: uses of 'rs' inherit the blocker on its declaration
|
||||
|
||||
21 | const rs = new RedeclareSub();
|
||||
22 | console.log(rs.read());
|
||||
| ^~~~~~~~~
|
||||
| ^~
|
||||
23 |
|
||||
|
||||
hint: the declaration of 'rs' did not compile — fix the diagnostic reported there and these sites clear with it
|
||||
|
||||
private-members.ts:44:51 - error SC1090: '#v in …' on 'unknown' receivers (only class-instance receivers — and unions of classes below or beside the declarer — have a static brand answer; narrow first) are not supported yet
|
||||
|
||||
43 | static probe(o: unknown): boolean {
|
||||
|
||||
@@ -41,13 +41,15 @@ stdlib-fence.ts:20:7 - error SC2020: 'NumberFormat' is part of the standard libr
|
||||
|
||||
hint: Intl formatter values have no representation — the COMPOSED en-US forms lower: new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options; the rest is ICU locale data the binary does not carry
|
||||
|
||||
stdlib-fence.ts:20:13 - error SC1090: constructing values other than classes declared in the program is not supported yet
|
||||
stdlib-fence.ts:20:13 - error SC2020: 'new Intl.NumberFormat' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
19 | const parsedMs = Date.parse;
|
||||
20 | const fmt = new Intl.NumberFormat();
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~
|
||||
21 |
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
stdlib-fence.ts:23:11 - error SC2020: 'new Date with 2 arguments' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
22 | // Constructors.
|
||||
@@ -136,15 +138,6 @@ stdlib-fence.ts:41:17 - error SC2020: 'number[].entries' is part of the standard
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
stdlib-fence.ts:43:14 - error SC2020: 'string.normalize' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
42 | const arrAt = [1, 2].at(0);
|
||||
43 | const norm = "abc".normalize();
|
||||
| ^~~~~~~~~~~~~~~
|
||||
44 | const limited = "a,b,c".split(Math.random() ? "," : /,/, 2); // union separators stay fenced; limits lower
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
stdlib-fence.ts:44:31 - error SC1090: '.split()' on a 'RegExp | string' separator (pass a string, or a regex literal) is not supported yet
|
||||
|
||||
43 | const norm = "abc".normalize();
|
||||
|
||||
@@ -102,10 +102,9 @@ test("settled generic rest-order fences count each source statement once", () =>
|
||||
expect(coverage.stats.statementsFailed).toBe(2);
|
||||
});
|
||||
|
||||
test("JS inference gaps land where 'any' lands: SC2011 static, island dynamic", async () => {
|
||||
// The js-gap fixture's tsconfig turns noImplicitAny off, so the untyped
|
||||
// parameter types `any` — the static analysis reports the site as
|
||||
// dynamic-capable (SC2011) while the JSDoc-typed neighbor stays static.
|
||||
test("inferred JavaScript stays static with or without the island enabled", async () => {
|
||||
// The js-gap fixture turns noImplicitAny off. Its inferred parameter
|
||||
// uses checked native values alongside the JSDoc-typed neighbor.
|
||||
await expect(report(fixture("js-gap/gap.js"))).toMatchFileSnapshot(
|
||||
"__snapshots__/coverage-js-gap.txt",
|
||||
);
|
||||
|
||||
@@ -273,13 +273,7 @@ console.log(new exports.O().a());
|
||||
expect(r.stderr).toMatch(/^Uncaught Error: .* \[SC1090 at .*extends-reassigned-property\.cjs:\d+\]\n$/);
|
||||
});
|
||||
|
||||
test("a LEAF class whose collection fenced defers to its statement (JS lane)", async () => {
|
||||
// The JS deferral's leaf edge: a class nothing extends keeps the
|
||||
// runtime-fence story even when its shape has no lowering
|
||||
// (constructor-assigned field shadowing its own method) — the build
|
||||
// succeeds, statements before the class statement run, and the class
|
||||
// statement throws the recorded fence. Only the EXTENDS edge reports
|
||||
// at compile time (diagnostics js-extends-poisoned-base pins that).
|
||||
test("a non-callable constructor field shadows its method and throws at the call (JS lane)", async () => {
|
||||
const r = await compileAndRun(
|
||||
"leaf-shadowing-class-defers",
|
||||
`'use strict';
|
||||
@@ -300,17 +294,10 @@ console.log("unreachable");
|
||||
);
|
||||
expect(r.exitCode).toBe(1);
|
||||
expect(r.stdout).toBe("before\n");
|
||||
expect(r.stderr).toMatch(
|
||||
/^Uncaught Error: constructor-assigned fields shadowing methods are not supported yet \[SC1090 at .*leaf-shadowing-class-defers\.cjs:5\]\n$/,
|
||||
);
|
||||
expect(r.stderr).toBe("Uncaught TypeError: x.m is not a function\n");
|
||||
});
|
||||
|
||||
test("a DERIVED class of a clean base keeps the deferral when only the derived is poisoned (JS lane)", async () => {
|
||||
// The other side of the extends-edge rule: the derived class's OWN
|
||||
// fence (a constructor-assigned field shadowing an INHERITED method)
|
||||
// stays deferred — the base collected fine, so the eager
|
||||
// poisoned-base report has nothing to flush, and the derived class
|
||||
// statement is the runtime fence exactly like a leaf.
|
||||
test("a callable constructor field shadows an inherited method (JS lane)", async () => {
|
||||
const r = await compileAndRun(
|
||||
"derived-shadowing-class-defers",
|
||||
`'use strict';
|
||||
@@ -331,11 +318,9 @@ console.log(i.foo());
|
||||
`,
|
||||
"cjs",
|
||||
);
|
||||
expect(r.exitCode).toBe(1);
|
||||
expect(r.stdout).toBe("before\n");
|
||||
expect(r.stderr).toMatch(
|
||||
/^Uncaught Error: constructor-assigned fields shadowing methods are not supported yet \[SC1090 at .*derived-shadowing-class-defers\.cjs:11\]\n$/,
|
||||
);
|
||||
expect(r.exitCode).toBe(0);
|
||||
expect(r.stdout).toBe("before\n3\n");
|
||||
expect(r.stderr).toBe("");
|
||||
});
|
||||
|
||||
test("destructuring a SURFACED builtin global's member is a named fence at the pattern (JS lane)", async () => {
|
||||
|
||||
@@ -942,16 +942,16 @@ describe.each(EMISSIONS)("K14: determinism fences, %s emission", (emission) => {
|
||||
|
||||
test("a code-keyed teachings entry rides a non-fence refusal as the attributed note", async () => {
|
||||
const diags = await refusal(
|
||||
`export function f(): number { return "abc".normalize().length; }\n`,
|
||||
`export function f(): number { return (1234.5).toLocaleString().length; }\n`,
|
||||
{
|
||||
exports: [{ export: "f", symbol: "kx_f", params: [], returns: "f64" }],
|
||||
determinism: { teachings: { SC2020: "host strings arrive normalized; never renormalize in a core" } },
|
||||
determinism: { teachings: { SC2020: "locale comes from the host; never infer it in a core" } },
|
||||
},
|
||||
emission,
|
||||
);
|
||||
expect(diags[0]!.code).toBe("SC2020");
|
||||
expect(diags[0]!.note).toBe(
|
||||
"from the 'refusal-fixture' profile: host strings arrive normalized; never renormalize in a core",
|
||||
"from the 'refusal-fixture' profile: locale comes from the host; never infer it in a core",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -337,11 +337,6 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => {
|
||||
expect(nativeRes.exitCode).toBe(nodeRes.exitCode);
|
||||
});
|
||||
|
||||
// Tier 1: fully static, byte-exact against Node. ms's driven surface —
|
||||
// BOTH the parse and format directions — joined when implicit-any
|
||||
// monomorphization and aliased-typeof narrowing landed; its one
|
||||
// remaining fence sits on the garbage-input path (pinned below), which
|
||||
// ms-cli.ts deliberately never drives.
|
||||
test.each(["release", "dev"] as const)("inferred class methods dispatch through inherited checked receivers (%s)", async (optimization) => {
|
||||
const entry = join(pilotRoot, "class-helper-cli.ts");
|
||||
const outDir = join(cacheDir, `class-helper-${optimization}-${sanitize ? "san" : "plain"}`);
|
||||
@@ -359,6 +354,7 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => {
|
||||
["escape-string-regexp", "escape-cli.ts"],
|
||||
["slash", "slash-cli.ts"],
|
||||
["ms", "ms-cli.ts"],
|
||||
["ms", "ms-invalid-cli.ts"],
|
||||
// dualist pins the "node" exports condition: Node runs ./node.js
|
||||
// (yaml's browser-vs-node shape) and the opted-in resolution must
|
||||
// land on the SAME artifact, never the browser build.
|
||||
@@ -515,26 +511,12 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => {
|
||||
]);
|
||||
}, 120_000);
|
||||
|
||||
// ms's coverage, pinned: aliased-typeof narrowing carried the entry
|
||||
// conditional (`var type = typeof val` — the checker only narrows const
|
||||
// aliases), and the whole driven surface is static. What remains is
|
||||
// parse()'s undefined-returning GARBAGE paths against its JSDoc
|
||||
// `@return {Number}` claim: two bare `return;`s stay runtime fences,
|
||||
// and the switch's `return undefined` now COMPILES to the stranded-unit
|
||||
// trap (divergence 335) — the same loud TypeError, thrown by compiled
|
||||
// code instead of a deferred fence. Node answers undefined there, a
|
||||
// value the declared representation cannot hold, so each path traps
|
||||
// loudly instead of misbehaving. The frontier only moves deliberately.
|
||||
test("ms compiles static with the JSDoc-contradicting undefined returns pinned", () => {
|
||||
const { coverage } = analyze(join(pilotRoot, "ms-cli.ts"), { npmStatic: ["ms"] });
|
||||
test("ms supports invalid duration strings without runtime fences", () => {
|
||||
const { coverage } = analyze(join(pilotRoot, "ms-invalid-cli.ts"), { npmStatic: ["ms"] });
|
||||
expect(coverage.npmStatic).toEqual([{ package: "ms", status: "static" }]);
|
||||
expect(coverage.preflightFailed).toBe(false);
|
||||
expect(coverage.diagnostics).toHaveLength(0); // builds — fences are runtime
|
||||
const fences = coverage.runtimeFences ?? [];
|
||||
expect(fences.length).toBe(2);
|
||||
for (const f of fences) {
|
||||
expect(f.message).toMatch(/bare 'return'/);
|
||||
}
|
||||
expect(coverage.diagnostics).toHaveLength(0);
|
||||
expect(coverage.runtimeFences ?? []).toEqual([]);
|
||||
}, 120_000);
|
||||
|
||||
// Commander is the declaration-backed npm-static vertical slice: its
|
||||
|
||||
@@ -17,7 +17,7 @@ test("production integer-range analysis lowers statically with identity-keyed ma
|
||||
const { coverage } = analyze(entry, { dynamic: false });
|
||||
expect(coverage.preflightFailed).toBe(false);
|
||||
expect(coverage.diagnostics).toEqual([]);
|
||||
expect(coverage.stats.statementsTotal).toBeGreaterThan(300);
|
||||
expect(coverage.stats.statementsTotal).toBeGreaterThan(250);
|
||||
expect(coverage.stats.statementsFailed).toBe(0);
|
||||
expect(coverage.stats.statementsIsland).toBe(0);
|
||||
expect(coverage.stats.functionsSkipped).toBe(0);
|
||||
|
||||
@@ -57,6 +57,8 @@ test.for(cases)("published three.js $fixture runs statically through LLVM on $ta
|
||||
const actual = wasm
|
||||
? run(process.execPath, ["--no-warnings", "-e", wasiRunner, result.binaryPath])
|
||||
: run(result.binaryPath, []);
|
||||
if (sanitize) actual.stderr = actual.stderr.split("\n").filter((line) =>
|
||||
!/^==\d+==WARNING: ASan doesn't fully support makecontext\/swapcontext/.test(line)).join("\n");
|
||||
expect(actual).toEqual(reference);
|
||||
} finally {
|
||||
if (previousTarget === undefined) delete process.env["SCRIPTC_TARGET"];
|
||||
|
||||
@@ -117,6 +117,14 @@ describe.skipIf(!zigOnPath())("wasm32-wasi differential", () => {
|
||||
"js-inferred-object-values.js",
|
||||
"js-static-accessors.js",
|
||||
"js-class-enumeration.js",
|
||||
"js-class-member-dispatch.js",
|
||||
"js-class-return-shapes.js",
|
||||
"js-optional-member-call.js",
|
||||
"js-promise-finally-order.js",
|
||||
"js-cjs-callable-snapshots/main.cjs",
|
||||
"js-inferred-callback-exports/main.js",
|
||||
"js-class-shadowed-methods.js",
|
||||
"js-default-listener-arguments.cjs",
|
||||
"js-reflect-receivers.js",
|
||||
"js-loose-dynamic-primitives.js",
|
||||
"js-promise-pending-reactions.js",
|
||||
|
||||
Reference in New Issue
Block a user