DataView setters, fresh-ArrayBuffer erasure, Object.is, and the en-US Intl slice

- The DataView setter family lands end-to-end; fresh ArrayBuffers erase into zero-filled typed-array views; resizable/shared surfaces fence by name
- Object.is compiles with the spec's SameValue across every kind, including union operands
- Composed en-US Intl.NumberFormat and toLocaleString print byte-exact through the Ryu digit core's shortest-round-trip rounding, 30k-double fuzz-verified; the rest of Intl fences by name with reasons
- DurationFormat deliberately fences: Node throws V8-internal Temporal errors with embedded source paths on plain inputs

# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
This commit is contained in:
Chris Tate
2026-07-23 22:20:44 -05:00
29 changed files with 1292 additions and 197 deletions
@@ -3,7 +3,7 @@
* emitter's frames (see the discipline comment in emitter core). */
import type { CEmitter, Temp } from "./emitter.js";
import { arrayOf, BOOL, BYTES_U8, bytesOf, canMarshalFuncIntoIsland, CHILDSTREAM_T, DYN, F64, IrExpr, IrRecordShape, IrType, isRefCounted, isUnitType, MAY_THROW_LIB_FNS, RUNTIME_ERROR_CLASSES, STRING, typeEquals } from "../../ir/nodes.js";
import { boxAccess, BYTES_NUM_KIND_C, BYTES_NUM_VAR_C, bytesElemKindC, cDecl, cFnPtrCast, cNumberLiteral, cStringLiteral, cType, DV_GET_KIND_C, elemAccess, mapKeyAccess, mapKeyKindC, mapValKindC, retainCallC, vAdapters } from "./emit-types.js";
import { boxAccess, BYTES_NUM_KIND_C, BYTES_NUM_VAR_C, bytesElemKindC, cDecl, cFnPtrCast, cNumberLiteral, cStringLiteral, cType, DV_GET_KIND_C, DV_SET_KIND_C, elemAccess, mapKeyAccess, mapKeyKindC, mapValKindC, retainCallC, vAdapters } from "./emit-types.js";
import { mangleClassNew, mangleClassRetain, mangleClassStruct, mangleField, mangleFnClosure, mangleFunction, mangleGlobal, mangleLocal, mangleRecordNew, mangleRecordStruct, mangleVtStruct } from "../mangle.js";
import { OVERFLOW_MEMBER } from "./emit-shapes.js";
import { dynDestrCheckHelper, dynIterNHelper, dynKeyGetHelper } from "./emit-walkers.js";
@@ -1137,6 +1137,24 @@ export function emitExpr(E: CEmitter, e: IrExpr): Temp {
E.emitPendingCheck();
return t;
}
case "dvSetUint8":
case "dvSetInt8":
case "dvSetUint16":
case "dvSetInt16":
case "dvSetUint32":
case "dvSetInt32":
case "dvSetFloat32":
case "dvSetFloat64": {
// DataView setters: [offset, value, littleEndian?] — void;
// throw the getters' constant RangeError on a bad offset.
const kind = DV_SET_KIND_C[method];
E.line(
`scr_dataview_set(${r.name}, ${args[0]!.name}, ${args[1]!.name}, ${kind}, ` +
`${args[2]?.name ?? "false"});${E.srcComment(e.loc)}`,
);
E.emitPendingCheck();
return { name: "", type: e.type };
}
default: {
const _exhaustive: never = method;
void _exhaustive;
@@ -1927,11 +1945,13 @@ export function emitExpr(E: CEmitter, e: IrExpr): Temp {
return E.newTemp(e.type, e.negated ? `!(${test})` : test);
}
case "unionEq": {
// Strict equality of the ARM values via the per-union helper (tag
// compare + per-arm payload compare). Both boxes are borrowed.
// Strict equality (or Object.is's SameValue — the f64 arm's
// compare is the one difference) of the ARM values via the
// per-union helper (tag compare + per-arm payload compare). Both
// boxes are borrowed.
const l = E.emitExpr(e.left);
const r = E.emitExpr(e.right);
const call = `${E.unionEqHelper(e.unionId)}(${l.name}, ${r.name})`;
const call = `${E.unionEqHelper(e.unionId, e.sameValue)}(${l.name}, ${r.name})`;
return E.newTemp(e.type, e.negated ? `!${call}` : call);
}
case "caughtTest": {
@@ -2480,6 +2500,13 @@ export function emitExpr(E: CEmitter, e: IrExpr): Temp {
return finish(`scr_num_to_exponential(${arg(0)})`);
case "num.toFixed0":
return finish(`scr_num_to_fixed0(${arg(0)})`);
// Object.is over two numbers — SameValue on doubles. No throw.
case "num.sameValue":
return finish(`scr_num_same_value(${arg(0)}, ${arg(1)})`);
// Intl.NumberFormat("en-US").format / toLocaleString("en-US")
// with default options (scr_lib.c). +1 string; no throw.
case "intl.numFormatEnUs":
return finish(`scr_intl_num_format_en_us(${arg(0)})`);
// The URL surface (scr_url.c): construction and the two
// fileURLToPath receiver forms throw catchable TypeErrors
// (may-throw seed set); the getters are pure reads. Receivers
@@ -585,6 +585,19 @@ export const DV_GET_KIND_C: Record<string, string> = {
dvGetBigInt64Number: "SCR_DV_BIGI64",
};
/** The runtime's ScrDataViewGet tag per dvSet* bytesIntrinsic method (the
* setters reuse the getter kinds; no BIG setters exist). */
export const DV_SET_KIND_C: Record<string, string> = {
dvSetUint8: "SCR_DV_U8",
dvSetInt8: "SCR_DV_I8",
dvSetUint16: "SCR_DV_U16",
dvSetInt16: "SCR_DV_I16",
dvSetUint32: "SCR_DV_U32",
dvSetInt32: "SCR_DV_I32",
dvSetFloat32: "SCR_DV_F32",
dvSetFloat64: "SCR_DV_F64",
};
/** Runtime accessor suffix for an element type: arrays store f64 and bool
* unboxed and everything refcounted as a pointer (`_ref`). */
export function elemAccess(elem: IrType): "f64" | "bool" | "ref" {
@@ -120,22 +120,26 @@ import { OVERFLOW_MEMBER } from "./emit-shapes.js";
return name;
}
/** The per-union strict-equality helper (interned per unionId):
/** The per-union strict-equality helper (interned per unionId, with a
* separate SameValue variant per unionId when Object.is reaches it):
* different tags are never equal; equal tags compare the arm values —
* unit arms equal, f64 with C == (NaN !== NaN, +0 === -0), strings by
* bytes, bools by value, ref arms by POINTER identity (JS object
* equality). Borrows both operands. */
export function unionEqHelper(E: CEmitter, unionId: string): string {
const existing = E.unionEqFns.get(unionId);
* unit arms equal, f64 with C == (NaN !== NaN, +0 === -0) or SameValue
* (NaN equals NaN, +0 differs from -0) under the sameValue flag,
* strings by bytes, bools by value, ref arms by POINTER identity (JS
* object equality). Borrows both operands. */
export function unionEqHelper(E: CEmitter, unionId: string, sameValue: boolean): string {
const key = sameValue ? `sv:${unionId}` : unionId;
const existing = E.unionEqFns.get(key);
if (existing) return existing;
const def = E.unionsById.get(unionId);
if (!def) throw new Error(`emitter bug: equality of unknown union ${unionId}`);
const name = `sc_ue_${E.unionEqFns.size}`;
E.unionEqFns.set(unionId, name);
E.unionEqFns.set(key, name);
const what = sameValue ? "SameValue" : "strict equality";
const sig = `static bool ${name}(ScrUnion *a, ScrUnion *b)`;
E.walkerProtos.push(`${sig}; /* strict equality ${unionId} */`);
E.walkerProtos.push(`${sig}; /* ${what} ${unionId} */`);
const d: string[] = [
`${sig} { /* strict equality ${unionId} */`,
`${sig} { /* ${what} ${unionId} */`,
` if (a->tag != b->tag) return false;`,
` switch (a->tag) {`,
];
@@ -146,7 +150,11 @@ import { OVERFLOW_MEMBER } from "./emit-shapes.js";
d.push(` case ${i}: return true;`);
break;
case "f64":
d.push(` case ${i}: return scr_union_get_f64(a) == scr_union_get_f64(b);`);
d.push(
sameValue
? ` case ${i}: return scr_num_same_value(scr_union_get_f64(a), scr_union_get_f64(b));`
: ` case ${i}: return scr_union_get_f64(a) == scr_union_get_f64(b);`,
);
break;
case "bool":
d.push(` case ${i}: return scr_union_get_bool(a) == scr_union_get_bool(b);`);
@@ -1076,8 +1076,8 @@ export class CEmitter {
return unionTruthyHelper(this, unionId);
}
unionEqHelper(unionId: string): string {
return unionEqHelper(this, unionId);
unionEqHelper(unionId: string, sameValue: boolean): string {
return unionEqHelper(this, unionId, sameValue);
}
unionToStrHelper(unionId: string): string {
+41 -1
View File
@@ -185,6 +185,8 @@ const LIB_FN_SYMS: Record<string, string> = {
"dyn.errInstanceof": "scr_dyn_err_instanceof",
"num.toExponential": "scr_num_to_exponential",
"num.toFixed0": "scr_num_to_fixed0",
"num.sameValue": "scr_num_same_value",
"intl.numFormatEnUs": "scr_intl_num_format_en_us",
"number.isFinite": "scr_num_is_finite",
"number.isNaN": "scr_num_is_nan",
"number.isInteger": "scr_num_is_integer",
@@ -772,6 +774,19 @@ const DV_GET_KIND: Record<string, number> = {
dvGetBigInt64Number: 9,
};
/** ScrDataViewGet per dvSet* method (the setters reuse the getter kinds;
* no BIG setters exist). */
const DV_SET_KIND: Record<string, number> = {
dvSetUint8: 0,
dvSetInt8: 1,
dvSetUint16: 2,
dvSetInt16: 3,
dvSetUint32: 4,
dvSetInt32: 5,
dvSetFloat32: 6,
dvSetFloat64: 7,
};
class LlEmitter {
/** Interned string literals: UTF-8 text → { symbol, byte length } —
* first-use order, the C emitter's determinism discipline. */
@@ -4363,7 +4378,14 @@ class LlEmitter {
const t = B.tmp();
B.line(`${a} = call double @scr_union_get_f64(ptr ${l.name})`);
B.line(`${b} = call double @scr_union_get_f64(ptr ${r.name})`);
B.line(`${t} = fcmp oeq double ${a}, ${b}`);
if (e.sameValue) {
// Object.is's f64 compare: NaN equals NaN, +0 differs
// from -0 — the runtime SameValue.
this.declare(`declare zeroext i1 @scr_num_same_value(double, double)`);
B.line(`${t} = call zeroext i1 @scr_num_same_value(double ${a}, double ${b})`);
} else {
B.line(`${t} = fcmp oeq double ${a}, ${b}`);
}
B.line(`store i1 ${t}, ptr ${slot}`);
break;
}
@@ -8889,6 +8911,24 @@ class LlEmitter {
false,
true,
);
case "dvSetUint8":
case "dvSetInt8":
case "dvSetUint16":
case "dvSetInt16":
case "dvSetUint32":
case "dvSetInt32":
case "dvSetFloat32":
case "dvSetFloat64":
// DataView setters: [offset, value, littleEndian?] — void; throw
// the getters' constant RangeError on a bad offset.
return call(
"scr_dataview_set",
"void (ptr, double, double, i32, i1 zeroext)",
`ptr ${r.name}, double ${args[0]!.name}, double ${args[1]!.name}, ` +
`i32 ${DV_SET_KIND[method]}, i1 ${args[2]?.name ?? "false"}`,
false,
true,
);
default: {
const _exhaustive: never = method;
void _exhaustive;
@@ -10,12 +10,12 @@ import { isJsSourceFile, locOf } from "../program.js";
import { isGenericCallableMemberType, typeKey } from "../types.js";
import { PoisonError, dynFallbackType, dynUndefinedExpr, importCallHandleType, jsFuncNameOf, newFnCtx } from "./lowerer.js";
import { enforceLibBoundary } from "./lib-boundary.js";
import { builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
import { NARROW_FIRST, builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
import { requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
import { mixinFnShapeOf } from "./lower-mixins.js";
import { bufEncoding, dynStringReceiver, lowerArrayFromCall, lowerDynArrayFilterCall, lowerDynArrayFlatMapCall, lowerGroupByStaticCall, lowerIteratorHelperCall, lowerObjectAssignIndexShape, lowerObjectFromEntriesCall, lowerObjectIterOverIndexShape, lowerRegexMethodCall, lowerStringMethodCall, lowerTupleReadMethodCall } from "./lower-containers.js";
import { lowerChildStreamMethodCall, lowerDirentMethodCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerWatcherMethodCall } from "./lower-builtins.js";
import { lowerPromiseAllTupleCall, lowerPromiseRejectCall, probeLower, templateRawTextOf } from "./lower-exprs.js";
import { droppableStatic, lowerPromiseAllTupleCall, lowerPromiseRejectCall, probeLower, templateRawTextOf } from "./lower-exprs.js";
import { httpClientFnBindingOf, isStreamUndefCallExpr, lowerHttpClientFnCall } from "./lower-server.js";
import { EMITTER_API_MEMBERS, exactInstanceClassOf, findGenericMethodOn, lowerClassGenericMethodCall, lowerStaticMethodCall, type ClassInfo } from "./lower-classes.js";
import { emitterRooted, lowerEmitterMethodCall } from "./lower-emitter.js";
@@ -3589,6 +3589,9 @@ export function lowerCall(L: Lowerer, expr: ts.CallExpression): IrExpr {
lowerSymbolStaticCall(L, expr, expr.expression) ??
lowerSymbolMethodCall(L, expr, expr.expression) ??
lowerRegExpStaticCall(L, expr, expr.expression) ??
// The composed en-US Intl.NumberFormat form — before the member
// fences (the receiver's Intl.NumberFormat type has no mapping).
lowerIntlNumberFormatCall(L, expr, expr.expression) ??
lowerGroupByStaticCall(L, expr, expr.expression) ??
// Iterator-helper chains rooted at arr.values() — before the
// array method paths (the terminal names collide with array
@@ -4286,6 +4289,43 @@ const inliningPredicates = new Set<ts.Symbol>();
const fn = name === "toExponential" ? "num.toExponential" : "num.toFixed0";
return { kind: "libCall", fn, args: [operand], type: STRING, loc };
}
// Number.prototype.toLocaleString("en-US") — the spec makes it
// NumberFormat(locale).format(this), so the en-US embedded formatter
// answers exactly. The unlowered forms fence by NAME: no locale (the
// host environment's default, which a compiled binary cannot carry),
// other locales (ICU data the binary does not embed), options bags.
if (name === "toLocaleString" && recvKind === "f64") {
if (call.arguments.length === 0) {
L.noLowering(
"Number.prototype.toLocaleString without a locale",
call,
"the default locale is the host environment's, which a compiled binary cannot carry — " +
'pass it explicitly: x.toLocaleString("en-US")',
);
}
if (call.arguments.length > 1) {
L.noLowering(
"Number.prototype.toLocaleString with an options bag",
call,
"the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction " +
'digits, grouping) — x.toLocaleString("en-US")',
);
}
const locNode = call.arguments[0]!;
if (ts.isSpreadElement(locNode) || !ts.isStringLiteralLike(locNode) || locNode.text !== "en-US") {
L.noLowering(
!ts.isSpreadElement(locNode) && ts.isStringLiteralLike(locNode)
? `Number.prototype.toLocaleString at locale "${locNode.text}"`
: "Number.prototype.toLocaleString with a non-literal locale",
locNode,
'"en-US" (Node\'s default-build locale) is the one locale whose data the runtime embeds — ' +
"everything else is ICU data the binary does not carry",
);
}
const operand = L.lowerExpr(recv);
if (operand.type.kind !== "f64") return null;
return { kind: "libCall", fn: "intl.numFormatEnUs", args: [operand], type: STRING, loc };
}
if (name === "charAt" && recvKind === "string" && call.arguments.length === 1 &&
!ts.isSpreadElement(call.arguments[0]!)) {
const receiver = L.lowerExpr(recv);
@@ -6147,11 +6187,217 @@ export function lowerPromiseMethodCall(L: Lowerer, call: ts.CallExpression,
return { kind: "libCall", fn: "regexp.escape", args: [arg], type: STRING, loc: locOf(call) };
}
/** The composed en-US Intl.NumberFormat form: `new Intl.NumberFormat(
* "en-US").format(x)` (and the callable spelling without `new` — the
* spec makes them the same formatter). Only the COMPOSED form lowers —
* formatter values have no representation — and only the one locale
* whose data the runtime embeds, with default options: decimal
* notation, 0–3 fraction digits rounded half-up on the shortest
* round-tripping decimal (ICU's rounding input — format(1.0005) is
* "1.001" though toFixed(3) answers "1.000"), "," grouping. The
* unlowered forms fence by NAME (no locale — the host environment's
* default, which a compiled binary cannot carry; other locales — ICU
* data the binary does not embed; options bags; non-number arguments).
* Null when the callee isn't a NumberFormat-construction .format. */
function lowerIntlNumberFormatCall(L: Lowerer, call: ts.CallExpression,
access: ts.PropertyAccessExpression,): IrExpr | null {
if (call.questionDotToken || access.questionDotToken) return null;
if (access.name.text !== "format") return null;
let recv: ts.Expression = access.expression;
while (ts.isParenthesizedExpression(recv)) recv = recv.expression;
let ctorArgs: readonly ts.Expression[];
if (ts.isNewExpression(recv) || (ts.isCallExpression(recv) && !recv.questionDotToken)) {
const ctor = recv.expression;
if (
!ts.isPropertyAccessExpression(ctor) || ctor.questionDotToken ||
ctor.name.text !== "NumberFormat" || !L.isStdlibGlobal(ctor.expression, "Intl")
) {
return null;
}
ctorArgs = recv.arguments ?? [];
} else {
return null;
}
const loc = locOf(call);
if (ctorArgs.length === 0) {
L.noLowering(
"Intl.NumberFormat without a locale",
recv,
"the default locale is the host environment's, which a compiled binary cannot carry — " +
'pass it explicitly: new Intl.NumberFormat("en-US").format(x)',
);
}
if (ctorArgs.length > 1) {
L.noLowering(
"Intl.NumberFormat with an options bag",
ctorArgs[1]!,
"the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction digits, " +
'grouping) — new Intl.NumberFormat("en-US").format(x)',
);
}
const locArg = ctorArgs[0]!;
if (ts.isSpreadElement(locArg) || !ts.isStringLiteralLike(locArg) || locArg.text !== "en-US") {
L.noLowering(
!ts.isSpreadElement(locArg) && ts.isStringLiteralLike(locArg)
? `Intl.NumberFormat at locale "${locArg.text}"`
: "Intl.NumberFormat with a non-literal locale",
locArg,
'"en-US" (Node\'s default-build locale) is the one locale whose data the runtime embeds — ' +
"everything else is ICU data the binary does not carry",
);
}
if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0]!)) {
L.noLowering(`Intl.NumberFormat("en-US").format with ${call.arguments.length} arguments`, call);
}
const argNode = call.arguments[0]!;
if (L.mapTypeOf(L.typeOf(argNode))?.kind !== "f64") {
L.noLowering(
`Intl.NumberFormat("en-US").format over a '${L.checker.typeToString(L.typeOf(argNode))}'`,
argNode,
"a number argument is the lowered form (bigint and numeric-string inputs have no representation)",
);
}
const arg = L.lowerExprExpecting(argNode, F64);
if (arg.type.kind !== "f64") L.badType(argNode, L.typeOf(argNode));
return { kind: "libCall", fn: "intl.numFormatEnUs", args: [arg], type: STRING, loc };
}
/** Object.is over statically disjoint kinds: the constant false, with
* both operands still evaluated for their effects (droppable statics
* fold away — JS evaluates arguments, but nothing observes a pure one). */
function objectIsDisjointFalse(left: IrExpr, right: IrExpr, loc: SrcLoc): IrExpr {
const stmts: IrStmt[] = [];
for (const e of [left, right]) {
if (!droppableStatic(e)) stmts.push({ kind: "exprStmt", expr: e, loc });
}
const answer: IrExpr = { kind: "boolLit", value: false, type: BOOL, loc };
if (stmts.length === 0) return answer;
return { kind: "seqExpr", stmts, result: answer, type: BOOL, loc };
}
function lowerObjectStaticCall(L: Lowerer, call: ts.CallExpression,
access: ts.PropertyAccessExpression,): IrExpr | null {
if (call.questionDotToken || access.questionDotToken) return null;
if (!L.isStdlibGlobal(access.expression, "Object")) return null;
const member = access.name.text;
// Object.is — the spec's SameValue over the static kinds. Number
// pairs take the runtime SameValue (NaN equals NaN, +0 differs from
// -0 — the two divergences from ===); every other supported pair
// rides exactly the strict-equality machinery, whose answers
// SameValue shares: strings by bytes, bools by value, unit literals
// by tag, unions per arm (a number arm's payload compare upgrades to
// SameValue via unionEq's flag), and the reference kinds by pointer
// identity. Statically DISJOINT kind pairs answer the constant false
// with the operands still evaluated (tsc admits any pair — Object.is
// is (any, any) — and JS evaluates the arguments either way).
// dyn/jsval operands keep strict equality's stance: validate first.
if (member === "is") {
if (call.arguments.length !== 2 || call.arguments.some((a) => ts.isSpreadElement(a))) {
L.noLowering(
`Object.is with ${call.arguments.length} arguments`,
call,
"exactly two arguments are the lowered form (JS treats a missing one as undefined — pass it explicitly)",
);
}
const loc = locOf(call);
const leftNode = call.arguments[0]!;
const rightNode = call.arguments[1]!;
const left = L.lowerExpr(leftNode);
const right = L.lowerExpr(rightNode);
const lk = left.type.kind;
const rk = right.type.kind;
if (lk === "f64" && rk === "f64") {
return { kind: "libCall", fn: "num.sameValue", args: [left, right], type: BOOL, loc };
}
if (left.type.kind === "string" && right.type.kind === "string") {
return { kind: "strEq", negated: false, left, right, type: BOOL, loc };
}
if (lk === "bool" && rk === "bool") {
return { kind: "bin", op: "===", left, right, type: BOOL, loc };
}
const unitTest = L.lowerUnitComparison(left, right, false, loc);
if (unitTest) return unitTest;
if (lk === "dyn" || rk === "dyn" || lk === "jsval" || rk === "jsval") {
L.noLowering(
"Object.is over a dynamic operand",
call,
"validate/narrow the value first (strict equality's rule) — SameValue only differs from === on numbers (NaN, ±0)",
);
}
if (left.type.kind === "union" || right.type.kind === "union") {
const ut = left.type.kind === "union" ? left.type : (right.type as IrType & { kind: "union" });
const bothUnion = left.type.kind === "union" && right.type.kind === "union";
const sameUnion = bothUnion && typeEquals(left.type, right.type);
if ((sameUnion || !bothUnion) && L.eqComparableUnion(ut.unionId)) {
const plain = left.type.kind === "union" ? right : left;
const arms = L.unions.get(ut.unionId)?.arms ?? [];
// The plain side wraps into the union exactly like === when the
// union holds its type; a plain PRIMITIVE the union has no arm
// for is the disjoint constant false (coercing it would strand).
if (bothUnion || arms.some((a) => typeEquals(a, plain.type))) {
const sameValue = arms.some((a) => a.kind === "f64");
return {
kind: "unionEq",
unionId: ut.unionId,
negated: false,
sameValue,
left: L.coerceInto(leftNode, left, ut),
right: L.coerceInto(rightNode, right, ut),
type: BOOL,
loc,
};
}
if (
plain.type.kind === "f64" || plain.type.kind === "string" ||
plain.type.kind === "bool" || isUnitType(plain.type)
) {
return objectIsDisjointFalse(left, right, loc);
}
}
L.noLowering(
"Object.is over these union operands",
call,
`union-typed comparisons need one comparable shape (${NARROW_FIRST})`,
);
}
// Reference kinds: pointer identity — exactly strict equality
// (hierarchy-related classes widen the derived side first).
let idLeft = left;
let idRight = right;
if (left.type.kind === "object" && right.type.kind === "object") {
if (L.isSubclassOf(left.type.className, right.type.className)) {
idLeft = L.upcastTo(left, right.type.className);
} else if (L.isSubclassOf(right.type.className, left.type.className)) {
idRight = L.upcastTo(right, left.type.className);
}
}
if (
(idLeft.type.kind === "func" && idRight.type.kind === "func") ||
(idLeft.type.kind === "classval" && idRight.type.kind === "classval")
) {
return { kind: "bin", op: "===", left: idLeft, right: idRight, type: BOOL, loc };
}
if (
(idLeft.type.kind === "array" || idLeft.type.kind === "map" ||
idLeft.type.kind === "set" || idLeft.type.kind === "object" ||
idLeft.type.kind === "record" || idLeft.type.kind === "symbol" ||
idLeft.type.kind === "bytes" || idLeft.type.kind === "promise") &&
typeEquals(idLeft.type, idRight.type)
) {
return { kind: "bin", op: "===", left: idLeft, right: idRight, type: BOOL, loc };
}
// Statically disjoint pairs with a primitive/unit side: SameValue
// never crosses kinds, so the answer is the constant false.
const disjoint = new Set(["f64", "string", "bool", "undefinedT", "nullT"]);
if (lk !== rk && (disjoint.has(lk) || disjoint.has(rk))) {
return objectIsDisjointFalse(left, right, loc);
}
L.noLowering(
`Object.is over '${L.fmt(left.type)}' and '${L.fmt(right.type)}' operands`,
call,
"the operands must share one comparable kind (numbers, strings, booleans, units, one union shape, or one reference type)",
);
}
// `Object.assign(fn, { props })` whose RESULT type maps to the hybrid
// (function-with-properties) record: the chalk-shape CONSTRUCTOR.
if (member === "assign") {
@@ -5330,6 +5330,7 @@ export function lowerNew(L: Lowerer, expr: ts.NewExpression): IrExpr {
WeakRef: "deref()-after-collect exposes GC timing — genuinely dynamic; hold a strong reference instead",
FinalizationRegistry: "finalization callbacks expose GC timing — genuinely dynamic; release resources explicitly instead",
SharedArrayBuffer: "no shared-memory threads exist in a compiled program — Uint8Array is the byte storage",
ArrayBuffer: "no free-standing ArrayBuffer value exists — typed arrays own their storage: allocate the view directly (new Uint8Array(n)), or erase a fresh buffer into one (new Uint8Array(new ArrayBuffer(n)), new DataView(new ArrayBuffer(n), ...))",
Proxy: "property-access metaprogramming has no static lowering (every property read must resolve at compile time)",
Function: "runtime code generation cannot be compiled ahead of time (the eval stance) — write the function",
};
@@ -4400,30 +4400,44 @@ const BYTES_CTORS: Record<string, IrBytesElem | undefined> = {
const seed: IrExpr = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
return { kind: "bytesNew", source: seed, type, loc };
}
// The SYNTACTIC `new T(new SharedArrayBuffer(n))` form — the
// Atomics.wait sleep idiom's construction. The buffer never exists
// as a value: n must be a byte-length LITERAL divisible by the
// element size (tsc would admit any number; a bad one is Node's
// RangeError — rejected at compile time instead of half-lowering),
// and the whole expression is a zero-filled typed array of
// n/elemSize elements. Sharing is unobservable without threads
// (scriptc has none), so erasing the SharedArrayBuffer is exact —
// SEMANTICS.md documents the stance.
// The SYNTACTIC `new T(new ArrayBuffer(n))` and `new T(new
// SharedArrayBuffer(n))` forms — fresh-buffer construction (the
// shared spelling is the Atomics.wait sleep idiom's). The buffer
// never exists as a value: n must be a byte-length LITERAL divisible
// by the element size (tsc would admit any number; a bad one is
// Node's RangeError — rejected at compile time instead of
// half-lowering), and the whole expression is a zero-filled typed
// array of n/elemSize elements. Erasing the buffer is exact: nothing
// else can ever reference it, so neither sharing (scriptc has no
// threads) nor aliasing is observable — SEMANTICS.md documents the
// stance. The RESIZABLE form (a maxByteLength options bag) fences by
// name: resize needs the buffer to exist as a value, and none does.
if (
ts.isNewExpression(argNode) &&
ts.isIdentifier(argNode.expression) &&
argNode.expression.text === "SharedArrayBuffer" &&
(argNode.expression.text === "ArrayBuffer" ||
argNode.expression.text === "SharedArrayBuffer") &&
L.isStdlibSymbol(L.resolveValueSymbol(argNode.expression) ?? undefined)
) {
const bufCtor = argNode.expression.text;
if ((argNode.arguments?.length ?? 0) > 1) {
L.noLowering(
`new ${name} over a resizable ${bufCtor}`,
argNode,
"a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer " +
"to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here " +
"erases into the view): drop the options bag",
);
}
const elemSize = elem === "u8" ? 1 : 4;
const lenArg = argNode.arguments?.length === 1 ? argNode.arguments[0] : undefined;
const lenT = lenArg ? L.typeOf(lenArg) : null;
const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
if (byteLen === null || byteLen % elemSize !== 0 || byteLen < 0) {
L.noLowering(
`new ${name} over this SharedArrayBuffer`,
`new ${name} over this ${bufCtor}`,
argNode,
`the byte length must be a number literal divisible by ${elemSize} — new ${name}(new SharedArrayBuffer(${elemSize}))`,
`the byte length must be a number literal divisible by ${elemSize} — new ${name}(new ${bufCtor}(${elemSize}))`,
);
}
const count: IrExpr = { kind: "numLit", value: byteLen / elemSize, type: F64, loc };
@@ -4477,9 +4491,48 @@ const BYTES_CTORS: Record<string, IrBytesElem | undefined> = {
"supported: new DataView(x.buffer), (x.buffer, byteOffset), or (x.buffer, byteOffset, byteLength)",
);
}
// The FRESH-BUFFER form: `new DataView(new ArrayBuffer(n), ...)` — the
// buffer erases into the view (nothing else can ever reference it, so
// the aliasing a shared buffer would exhibit is unobservable): the
// view's storage is a fresh zero-filled n-byte allocation. n must be a
// non-negative integer LITERAL (tsc admits any number; a fractional
// one is ToIndex truncation nothing here implements). byteOffset and
// byteLength keep their runtime Node-RangeError story. The RESIZABLE
// form (a maxByteLength options bag) fences by name.
if (
ts.isNewExpression(bufNode) &&
ts.isIdentifier(bufNode.expression) &&
bufNode.expression.text === "ArrayBuffer" &&
L.isStdlibSymbol(L.resolveValueSymbol(bufNode.expression) ?? undefined)
) {
if ((bufNode.arguments?.length ?? 0) > 1) {
L.noLowering(
"new DataView over a resizable ArrayBuffer",
bufNode,
"a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer " +
"to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here " +
"erases into the view): drop the options bag",
);
}
const lenArg = bufNode.arguments?.length === 1 ? bufNode.arguments[0] : undefined;
const lenT = lenArg ? L.typeOf(lenArg) : null;
const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
if (byteLen === null || !Number.isInteger(byteLen) || byteLen < 0) {
L.noLowering(
"new DataView over this ArrayBuffer",
bufNode,
"the byte length must be a non-negative integer literal — new DataView(new ArrayBuffer(8))",
);
}
const count: IrExpr = { kind: "numLit", value: byteLen, type: F64, loc };
const receiver: IrExpr = { kind: "bytesNew", source: count, type: BYTES_U8, loc };
const idxArgs = args.slice(1).map((a) => L.lowerExprExpecting(a, F64));
return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
}
const hint =
"views compile over a typed array's own storage — new DataView(x.buffer, byteOffset?, byteLength?) " +
"where x is a Uint8Array/Uint32Array/Float32Array/Buffer value; free-standing ArrayBuffers have no representation";
"where x is a Uint8Array/Uint32Array/Float32Array/Buffer value — or a fresh buffer erased into " +
"the view: new DataView(new ArrayBuffer(n), ...); free-standing ArrayBuffers have no representation";
if (!ts.isPropertyAccessExpression(bufNode) || bufNode.name.text !== "buffer" || bufNode.questionDotToken) {
L.noLowering("new DataView over this buffer expression", bufNode, hint);
}
@@ -4667,6 +4720,26 @@ const BYTES_CTORS: Record<string, IrBytesElem | undefined> = {
if (nArgs === 2) args.push(L.lowerExprExpecting(call.arguments[1]!, BOOL));
return { kind: "bytesIntrinsic", method: dvGetter.method, receiver, args, type: F64, loc };
}
// DataView setters — the getters' mirror: (byteOffset, value) plus the
// optional littleEndian bool on the multi-byte kinds. Void results;
// the same constant Node RangeError on a bad offset (may-throw seeds).
// setBigUint64/setBigInt64 never lower (bigint ARGUMENTS have no
// representation and no composed form exists) — they keep the member
// fence, as does setFloat16.
const dvSetter = own(DV_SETTERS, name);
if (dvSetter !== undefined && receiverIr.elem === "u8") {
const maxArgs = dvSetter.le ? 3 : 2;
if (nArgs < 2 || nArgs > maxArgs) {
L.noLowering(`.${name} with ${nArgs} arguments`, call);
}
const receiver = L.lowerExpr(access.expression);
const args = [
L.lowerExprExpecting(call.arguments[0]!, F64),
L.lowerExprExpecting(call.arguments[1]!, F64),
];
if (nArgs === 3) args.push(L.lowerExprExpecting(call.arguments[2]!, BOOL));
return { kind: "bytesIntrinsic", method: dvSetter.method, receiver, args, type: VOID, loc };
}
return null;
}
@@ -4909,6 +4982,17 @@ const DV_GETTERS: Record<string, { method: IrBytesIntrinsicMethod; le: boolean }
getBigInt64: { method: "dvGetBigInt64Number", le: true },
};
const DV_SETTERS: Record<string, { method: IrBytesIntrinsicMethod; le: boolean } | undefined> = {
setUint8: { method: "dvSetUint8", le: false },
setInt8: { method: "dvSetInt8", le: false },
setUint16: { method: "dvSetUint16", le: true },
setInt16: { method: "dvSetInt16", le: true },
setUint32: { method: "dvSetUint32", le: true },
setInt32: { method: "dvSetInt32", le: true },
setFloat32: { method: "dvSetFloat32", le: true },
setFloat64: { method: "dvSetFloat64", le: true },
};
/** The Buffer statics — `Buffer.from(...)`, `Buffer.alloc(n)`,
* `Buffer.concat(list)`, `Buffer.isBuffer(x)` — on THE stdlib Buffer
* global (name + provenance; fallback and @types/node alike). Null when
@@ -847,8 +847,9 @@ function lowerExprInner(L: Lowerer, expr: ts.Expression): IrExpr {
BigInt: "arbitrary-precision integers have no static representation — f64 is the one number type (Number.MAX_SAFE_INTEGER bounds exact integers)",
Proxy: "property-access metaprogramming has no static lowering (every property read must resolve at compile time)",
Reflect: "reflective property access has no static lowering — read and call members directly",
Intl: "locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data)",
Intl: "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",
SharedArrayBuffer: "no shared-memory threads exist in a compiled program — Uint8Array is the byte storage",
ArrayBuffer: "no free-standing ArrayBuffer value exists — typed arrays own their storage (new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view)",
WeakRef: "deref()-after-collect exposes GC timing — genuinely dynamic; hold a strong reference instead",
FinalizationRegistry: "finalization callbacks expose GC timing — genuinely dynamic; release resources explicitly instead",
eval: "runtime code evaluation cannot be compiled ahead of time",
@@ -7133,6 +7134,7 @@ export function lowerBinary(L: Lowerer, expr: ts.BinaryExpression): IrExpr {
kind: "unionEq",
unionId: ut.unionId,
negated,
sameValue: false,
left: L.coerceInto(expr.left, left, ut),
right: L.coerceInto(expr.right, right, ut),
type: BOOL,
@@ -2822,6 +2822,7 @@ export function lowerVarDecl(L: Lowerer, decl: ts.VariableDeclaration, isLet: bo
kind: "unionEq",
unionId: unionType.unionId,
negated: false,
sameValue: false,
left: disc,
right: L.coerceInto(clause.expression, test, unionType),
type: BOOL,
@@ -2224,9 +2224,32 @@ export class Lowerer {
}
}
// STANDARD-LIBRARY nominal types with no lowering at all: the SC2020
// story, naming the type.
// story, naming the type — and for the families with a WHY, the same
// pointed reason the identifier/constructor chokepoints teach.
if (stdlibNominal) {
this.pushDiag(noLoweringDiag(this.checker.typeToString(type), locOf(node)));
const intlHint =
"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";
const typeHints: Record<string, string | undefined> = {
ArrayBuffer:
"no free-standing ArrayBuffer value exists — typed arrays own their storage " +
"(new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view)",
SharedArrayBuffer:
"no shared-memory threads exist in a compiled program — Uint8Array is the byte storage",
NumberFormat: intlHint,
DateTimeFormat: intlHint,
DurationFormat: intlHint,
PluralRules: intlHint,
Collator: intlHint,
ListFormat: intlHint,
RelativeTimeFormat: intlHint,
Segmenter: intlHint,
DisplayNames: intlHint,
};
this.pushDiag(
noLoweringDiag(this.checker.typeToString(type), locOf(node), typeHints[stdlibOwnSym?.name ?? ""]),
);
throw new PoisonError();
}
// USER record shapes blocked by ONE member (SC2009's record arm):
@@ -1038,9 +1038,18 @@ export const BUILTIN_MODULE_FENCE_HINTS: Record<string, Record<string, string |
hint =
"the regex-LITERAL argument forms lower (s.match(/re/)); a string pattern constructs " +
"a RegExp at runtime, which has no lowering";
} else if (container === "ArrayBuffer" || container.startsWith("ArrayBuffer<")) {
hint =
"resize/transfer/maxByteLength need the buffer to exist as a runtime value, and no " +
"free-standing ArrayBuffer value does — typed arrays own fixed-length storage " +
"(new Uint8Array(n)); allocate a new view and copy instead";
} else if (container === "SharedArrayBuffer" || container.startsWith("SharedArrayBuffer<")) {
hint =
"no shared-memory threads exist in a compiled program — Uint8Array is the byte storage " +
"(a fixed-length allocation: grow has nothing to share it with)";
} else if (
container === "Intl" || container.startsWith("Intl.") ||
["NumberFormat", "DateTimeFormat", "PluralRules", "Collator", "Locale",
["NumberFormat", "DateTimeFormat", "DurationFormat", "PluralRules", "Collator", "Locale",
"RelativeTimeFormat", "ListFormat", "Segmenter", "DisplayNames"].includes(container) ||
member === "toLocaleString" || member === "toLocaleDateString" ||
member === "toLocaleTimeString" || member === "toLocaleLowerCase" ||
@@ -1048,8 +1057,9 @@ export const BUILTIN_MODULE_FENCE_HINTS: Record<string, Record<string, string |
) {
hint =
"locale- and ICU-backed behavior lives outside the static runtime (the localeCompare " +
"stance: code-unit order, no collation/locale data) — format with template literals, " +
"toFixed, and toString instead";
"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; format with template literals, toFixed, and toString otherwise";
} else if (container === "Object" && member === "assign") {
hint =
"spread instead: { ...a, ...b } builds the merged record; what lowers: the empty-target " +
+42 -3
View File
@@ -1366,7 +1366,12 @@ export type IrStrIntrinsicMethod =
* DataView" RangeError on any bad offset. `dvGetBigUint64Number`/
* `dvGetBigInt64Number` are the COMPOSED `Number(view.getBigUint64(...))`
* lowerings — the bare bigint-returning calls are fenced, the wrapped
* form converts the 8-byte integer to double exactly as Number(bigint). */
* form converts the 8-byte integer to double exactly as Number(bigint).
* The `dvSet*` setters mirror the getters: [offset, value] plus the
* optional bool littleEndian on the multi-byte kinds → void, the same
* constant RangeError on any bad offset; values coerce JS-exactly
* (integer kinds by modular truncation, Float32 by double→float
* rounding). No BIG setters exist — bigint arguments never lower. */
export type IrBytesIntrinsicMethod =
| "length"
| "byteLength"
@@ -1419,7 +1424,15 @@ export type IrBytesIntrinsicMethod =
| "dvGetFloat32"
| "dvGetFloat64"
| "dvGetBigUint64Number"
| "dvGetBigInt64Number";
| "dvGetBigInt64Number"
| "dvSetUint8"
| "dvSetInt8"
| "dvSetUint16"
| "dvSetInt16"
| "dvSetUint32"
| "dvSetInt32"
| "dvSetFloat32"
| "dvSetFloat64";
/** The bytesIntrinsic methods that can raise a catchable error — backends'
* may-throw analyses seed on these exactly like MAY_THROW_LIB_FNS. */
@@ -1449,6 +1462,14 @@ export const MAY_THROW_BYTES_METHODS: ReadonlySet<IrBytesIntrinsicMethod> = new
"dvGetFloat64",
"dvGetBigUint64Number",
"dvGetBigInt64Number",
"dvSetUint8",
"dvSetInt8",
"dvSetUint16",
"dvSetInt16",
"dvSetUint32",
"dvSetInt32",
"dvSetFloat32",
"dvSetFloat64",
]);
/** The regex operation surface. Receiver/arg conventions (validated):
@@ -1749,6 +1770,20 @@ export type IrLibFn =
* forms keep their island lowering. Results +1; never throw. */
| "num.toExponential"
| "num.toFixed0"
/** Object.is over two numbers — the spec's SameValue on doubles: NaN
* equals NaN, +0 differs from -0, everything else is `===`. Plain bool
* result; never throws. (Union-armed operands take unionEq's sameValue
* flag instead — this is the both-f64 fast path.) */
| "num.sameValue"
/** `new Intl.NumberFormat("en-US").format(x)` and
* `x.toLocaleString("en-US")` with DEFAULT options — the one locale
* whose data the runtime embeds (Node's default-build locale): decimal
* notation, 0–3 fraction digits rounded half-up on the SHORTEST
* round-tripping decimal (ICU's rounding input, probed vs Node —
* format(1.0005) is "1.001" though toFixed(3) answers "1.000"), ","
* grouping every three integer digits, "∞"/"NaN" texts, and "-0" for
* negative inputs rounding to zero. Result +1; never throws. */
| "intl.numFormatEnUs"
/** `delete process.env[NAME]` — unsetenv(3): the mutation is visible to
* every later read (process.envGet asks getenv fresh) and inherited by
* spawned children, exactly Node. Statement position only (JS's boolean
@@ -4306,11 +4341,15 @@ export type IrExpr =
* comparison (`u === "text"`) arrives here after the frontend wraps the
* plain side (payload identity is preserved by the wrap, so ref-arm
* semantics stay JS-exact). Operands are borrowed; result is a plain
* bool. `negated` is the `!==` spelling. */
* bool. `negated` is the `!==` spelling. `sameValue` upgrades the f64
* arm's compare from `===` to SameValue (NaN equals NaN, +0 differs
* from -0) — the Object.is lowering; every other arm's compare is
* shared between the two semantics. */
| {
kind: "unionEq";
unionId: string;
negated: boolean;
sameValue: boolean;
left: IrExpr;
right: IrExpr;
type: IrType;
+19 -7
View File
@@ -209,6 +209,8 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
"regexp.escape": { argTypes: [STRING], result: STRING },
"num.toExponential": { argTypes: [F64], result: STRING },
"num.toFixed0": { argTypes: [F64], result: STRING },
"num.sameValue": { argTypes: [F64, F64], result: BOOL },
"intl.numFormatEnUs": { argTypes: [F64], result: STRING },
"sym.new": { argTypes: [STRING], result: SYMBOL_T },
"sym.newAnon": { argTypes: [], result: SYMBOL_T },
"sym.for": { argTypes: [STRING], result: SYMBOL_T },
@@ -2082,13 +2084,23 @@ function validateFunction(
? { argTypes: [STRING, F64, F64, F64], minArgs: 4, result: F64 }
: e.method === "dataViewNew"
? { argTypes: [F64, F64], minArgs: 0, result: BYTES_U8 }
: {
// dvGet*: the 8-bit getters take no littleEndian.
argTypes:
e.method === "dvGetUint8" || e.method === "dvGetInt8" ? [F64] : [F64, BOOL],
minArgs: 1,
result: F64,
});
: e.method.startsWith("dvSet")
? {
// dvSet*: [offset, value], the 8-bit setters take no littleEndian.
argTypes:
e.method === "dvSetUint8" || e.method === "dvSetInt8"
? [F64, F64]
: [F64, F64, BOOL],
minArgs: 2,
result: VOID,
}
: {
// dvGet*: the 8-bit getters take no littleEndian.
argTypes:
e.method === "dvGetUint8" || e.method === "dvGetInt8" ? [F64] : [F64, BOOL],
minArgs: 1,
result: F64,
});
if (e.args.length < sig.minArgs || e.args.length > sig.argTypes.length) {
err(`bytesIntrinsic ${e.method}: ${e.args.length} args`, e.loc);
}
@@ -4754,6 +4754,24 @@
],
"diags": []
},
"<repo>/tests/corpus/2560-arraybuffer-dataview-set.ts": {
"order": [
"<repo>/tests/corpus/2560-arraybuffer-dataview-set.ts"
],
"diags": []
},
"<repo>/tests/corpus/2561-object-is.ts": {
"order": [
"<repo>/tests/corpus/2561-object-is.ts"
],
"diags": []
},
"<repo>/tests/corpus/2562-intl-numberformat-en-us.ts": {
"order": [
"<repo>/tests/corpus/2562-intl-numberformat-en-us.ts"
],
"diags": []
},
"<repo>/tests/corpus/300-if-else.ts": {
"order": [
"<repo>/tests/corpus/300-if-else.ts"
@@ -6282,6 +6300,12 @@
],
"diags": []
},
"<repo>/tests/diagnostics/intl.ts": {
"order": [
"<repo>/tests/diagnostics/intl.ts"
],
"diags": []
},
"<repo>/tests/diagnostics/island-boundary-slots.ts": {
"order": [
"<repo>/tests/diagnostics/island-boundary-slots.ts"
@@ -6632,6 +6656,48 @@
],
"diags": []
},
"<repo>/tests/fixtures/node-types/argv-env.ts": {
"order": [
"<repo>/tests/fixtures/node-types/argv-env.ts"
],
"diags": []
},
"<repo>/tests/fixtures/node-types/fenced.ts": {
"order": [
"<repo>/tests/fixtures/node-types/fenced.ts"
],
"diags": []
},
"<repo>/tests/fixtures/node-types/import-fences.ts": {
"order": [
"<repo>/tests/fixtures/node-types/import-fences.ts"
],
"diags": [
{
"code": "SC1010",
"message": "the 'v8' module is not supported yet",
"loc": {
"file": "<repo>/tests/fixtures/node-types/import-fences.ts",
"start": 422,
"end": 461
},
"milestone": "M4",
"hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), installed npm packages (their code runs under --dynamic), and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, readline, events, stream, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, and node:test and node:module modules (bare or node:-prefixed) are supported"
}
]
},
"<repo>/tests/fixtures/node-types/path-os.ts": {
"order": [
"<repo>/tests/fixtures/node-types/path-os.ts"
],
"diags": []
},
"<repo>/tests/fixtures/node-types/stream-capture.ts": {
"order": [
"<repo>/tests/fixtures/node-types/stream-capture.ts"
],
"diags": []
},
"<repo>/tests/fixtures/npm/cases/2465-getter-table/main.ts": {
"order": [
"<repo>/tests/fixtures/npm/cases/2465-getter-table/main.ts"
@@ -7041,48 +7107,6 @@
"hint": "set \"strictNullChecks\": true (or \"strict\": true) in the project tsconfig"
}
]
},
"<repo>/tests/fixtures/node-types/argv-env.ts": {
"order": [
"<repo>/tests/fixtures/node-types/argv-env.ts"
],
"diags": []
},
"<repo>/tests/fixtures/node-types/fenced.ts": {
"order": [
"<repo>/tests/fixtures/node-types/fenced.ts"
],
"diags": []
},
"<repo>/tests/fixtures/node-types/import-fences.ts": {
"order": [
"<repo>/tests/fixtures/node-types/import-fences.ts"
],
"diags": [
{
"code": "SC1010",
"message": "the 'v8' module is not supported yet",
"loc": {
"file": "<repo>/tests/fixtures/node-types/import-fences.ts",
"start": 422,
"end": 461
},
"milestone": "M4",
"hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), installed npm packages (their code runs under --dynamic), and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, readline, events, stream, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, and node:test and node:module modules (bare or node:-prefixed) are supported"
}
]
},
"<repo>/tests/fixtures/node-types/path-os.ts": {
"order": [
"<repo>/tests/fixtures/node-types/path-os.ts"
],
"diags": []
},
"<repo>/tests/fixtures/node-types/stream-capture.ts": {
"order": [
"<repo>/tests/fixtures/node-types/stream-capture.ts"
],
"diags": []
}
}
}
+43
View File
@@ -312,6 +312,49 @@ double scr_dataview_get(const ScrBytes *b, double byte_off, ScrDataViewGet kind,
return 0; /* unreachable */
}
void scr_dataview_set(ScrBytes *b, double byte_off, double value, ScrDataViewGet kind, bool le) {
size_t width = scr_dataview_get_size(kind);
/* ToIndex + the view-relative bounds check — the getters' ONE message. */
double off = (byte_off != byte_off) ? 0 : trunc(byte_off);
if (!(off >= 0) || off > 9007199254740991.0 || off + (double)width > (double)b->len) {
static const char msg[] = "Offset is outside the bounds of the DataView";
scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
return;
}
/* Coerce to the stored bit pattern. The integer kinds share ToUint32's
* 2^32 residue (signed/unsigned store the same bits; narrower widths
* take the low bytes — 2^width divides 2^32, so the residues agree);
* F32 rounds double→float to nearest even, exactly the spec. */
uint64_t u = 0;
switch (kind) {
case SCR_DV_U8:
case SCR_DV_I8:
case SCR_DV_U16:
case SCR_DV_I16:
case SCR_DV_U32:
case SCR_DV_I32:
u = (uint64_t)scr_bytes_to_u32(value);
break;
case SCR_DV_F32: {
float f = (float)value;
uint32_t bits;
memcpy(&bits, &f, 4);
u = bits;
break;
}
case SCR_DV_F64:
memcpy(&u, &value, 8);
break;
default:
return; /* BIG kinds never lower (bigint arguments) */
}
/* Scatter host-independently; le false is the JS big-endian default. */
uint8_t *p = b->data + (size_t)off;
for (size_t i = 0; i < width; i++) {
p[le ? i : width - 1 - i] = (uint8_t)(u >> (8 * i));
}
}
/* ── encodings (u8 only — the compiler routes only u8 receivers here) ──── */
static const char scr_hex_digits[] = "0123456789abcdef";
+98
View File
@@ -3486,6 +3486,104 @@ ScrStr *scr_num_to_fixed0(double x) {
return r;
}
/* Increment a decimal digit string in place. Returns true on overflow —
* the value becomes 1 followed by len zeros (the caller folds the zeros
* into its scale); an EMPTY string increments to "1" the same way (the
* round-up-from-nothing case: 0.0005 at 3 fraction digits). */
static bool scr_dec_inc(char *d, int len) {
for (int i = len - 1; i >= 0; i--) {
if (d[i] != '9') {
d[i]++;
return false;
}
d[i] = '0';
}
d[0] = '1';
return true;
}
/* Intl.NumberFormat("en-US").format(x) / x.toLocaleString("en-US") with
* DEFAULT options: decimal notation, minimum 0 / maximum 3 fraction
* digits, "," grouping every three integer digits, "∞"/"NaN" texts, and
* "-0" whenever the input is negative or negative zero even after
* rounding to zero. Rounding is half-up ON THE SHORTEST ROUND-TRIPPING
* DECIMAL — ICU's rounding input, probed against Node: format(1.0005) is
* "1.001" although the double is 1.000499... and toFixed(3) answers
* "1.000"; format(1e23) prints the shortest form's trailing zeros, not
* the double's exact expansion. The en-US/latn symbols (",", ".", "∞",
* "NaN", group size 3) are the whole embedded locale surface. Verified
* differentially against Node. Result +1; never throws. */
ScrStr *scr_intl_num_format_en_us(double x) {
if (isnan(x)) return scr_str_new("NaN", 3);
if (isinf(x)) {
return x < 0 ? scr_str_new("-\xE2\x88\x9E", 4) : scr_str_new("\xE2\x88\x9E", 3);
}
bool neg = signbit(x) != 0;
if (x == 0) return neg ? scr_str_new("-0", 2) : scr_str_new("0", 1);
double a = neg ? -x : x;
/* Shortest digits: value = 0.d × 10^n (no trailing zeros, k ≤ 17). */
char d[18];
int n;
int k = scr_f64_digits(a, d, &n);
/* Round at 3 fraction digits: fraction position p is digit index
* n+p-1, so index n+3 is the first DROPPED digit. Half-up on the
* decimal digits — the shortest string ends right after them, so
* "first dropped digit ≥ 5" IS the whole decision. */
int keep = n + 3;
if (keep < k) {
bool up = keep >= 0 && d[keep] >= '5';
k = keep < 0 ? 0 : keep;
if (up && scr_dec_inc(d, k)) {
/* Carried out (all nines, or the round-up-from-nothing 0.0005
* case): one leading 1, the dropped nines fold into the scale. */
k = 1;
n += 1;
} else if (k == 0) {
/* Everything rounded away: ±0 with the sign preserved. */
return neg ? scr_str_new("-0", 2) : scr_str_new("0", 1);
}
}
/* Assemble: integer digits (indices [0, n)), zero-padded past k, then
* the ≤ 3 fraction digits (indices n..n+2, '0' outside [0, k)) with
* trailing zeros trimmed, then commas every three integer digits. */
char frac[3];
int flen = 0;
for (int p = 1; p <= 3; p++) {
int idx = n + p - 1;
frac[flen++] = (idx >= 0 && idx < k) ? d[idx] : '0';
}
while (flen > 0 && frac[flen - 1] == '0') flen--;
char out[512];
int o = 0;
if (neg) out[o++] = '-';
if (n <= 0) {
out[o++] = '0';
} else {
for (int i = 0; i < n; i++) {
if (i > 0 && (n - i) % 3 == 0) out[o++] = ',';
out[o++] = (i < k) ? d[i] : '0';
}
}
if (flen > 0) {
out[o++] = '.';
memcpy(out + o, frac, (size_t)flen);
o += flen;
}
return scr_str_new(out, (size_t)o);
}
/* Object.is over two numbers — the spec's SameValue on doubles: NaN
* equals NaN, +0 differs from -0, everything else is ==. */
bool scr_num_same_value(double a, double b) {
if (a != a) return b != b;
if (a == 0 && b == 0) return signbit(a) == signbit(b);
return a == b;
}
bool scr_num_is_nan(double x) { return isnan(x) != 0; }
bool scr_num_is_integer(double x) { return isfinite(x) && trunc(x) == x; }
+29 -19
View File
@@ -24,27 +24,19 @@
* unchanged. Provides d2d(), d2d_small_int(), decimalLength17(), div10(). */
#include "../vendor/ryu/d2s.c"
size_t scr_f64_to_str(double x, char *buf) {
if (isnan(x)) return (size_t)(stpcpy(buf, "NaN") - buf);
if (x == 0) return (size_t)(stpcpy(buf, "0") - buf); /* covers -0 */
if (isinf(x)) {
return (size_t)(stpcpy(buf, x < 0 ? "-Infinity" : "Infinity") - buf);
}
char *out = buf;
if (x < 0) {
*out++ = '-';
x = -x;
}
/* Shortest round-tripping digits via Ryū. Mirrors d2s_buffered_n's
* dispatch: the exact small-integer fast path first (trailing decimal
* zeros folded into the exponent), the full algorithm otherwise. */
/* The Ryū digit core, shared by the ECMA placement below and the Intl
* en-US number formatter (scr_lib.c): the shortest round-tripping digit
* string for a positive finite double — value = 0.digits × 10^n with no
* trailing zeros. Returns k (the digit count, ≤ 17); digits is
* NUL-terminated. Mirrors d2s_buffered_n's dispatch: the exact
* small-integer fast path first (trailing decimal zeros folded into the
* exponent), the full algorithm otherwise. */
int scr_f64_digits(double x, char digits[18], int *n_out) {
uint64_t bits;
memcpy(&bits, &x, sizeof bits);
const uint64_t ieeeMantissa = bits & ((1ull << DOUBLE_MANTISSA_BITS) - 1);
const uint32_t ieeeExponent =
(uint32_t)(bits >> DOUBLE_MANTISSA_BITS); /* sign already stripped */
(uint32_t)(bits >> DOUBLE_MANTISSA_BITS); /* sign must be stripped */
floating_decimal_64 v;
if (d2d_small_int(ieeeMantissa, ieeeExponent, &v)) {
for (;;) {
@@ -60,9 +52,8 @@ size_t scr_f64_to_str(double x, char *buf) {
/* Ryū's (mantissa, exponent) → ECMA's (digits, k, n): the k mantissa
* digits have no trailing zeros, and value = 0.digits * 10^n. */
char digits[18];
int k = (int)decimalLength17(v.mantissa);
int n = v.exponent + k;
*n_out = v.exponent + k;
digits[k] = '\0';
uint64_t m = v.mantissa;
for (int i = k - 1; i >= 0; i--) {
@@ -70,6 +61,25 @@ size_t scr_f64_to_str(double x, char *buf) {
digits[i] = (char)('0' + (uint32_t)m - 10 * (uint32_t)q);
m = q;
}
return k;
}
size_t scr_f64_to_str(double x, char *buf) {
if (isnan(x)) return (size_t)(stpcpy(buf, "NaN") - buf);
if (x == 0) return (size_t)(stpcpy(buf, "0") - buf); /* covers -0 */
if (isinf(x)) {
return (size_t)(stpcpy(buf, x < 0 ? "-Infinity" : "Infinity") - buf);
}
char *out = buf;
if (x < 0) {
*out++ = '-';
x = -x;
}
char digits[18];
int n;
int k = scr_f64_digits(x, digits, &n);
if (k <= n && n <= 21) {
/* Integer: digits followed by n-k zeros. */
+26
View File
@@ -3896,6 +3896,12 @@ void scr_jsval_cast_fail(ScrJsval *v, const ScrStr *target);
*/
size_t scr_f64_to_str(double x, char *buf);
/* The Ryū digit core (scr_number.c), shared with the Intl en-US number
* formatter: the shortest round-tripping digits of a POSITIVE finite
* double — value = 0.digits × 10^n, no trailing zeros, NUL-terminated.
* Returns k, the digit count (≤ 17). */
int scr_f64_digits(double x, char digits[18], int *n_out);
/* ToString for template literals / string coercion. Returns +1. */
ScrStr *scr_f64_to_scrstr(double x);
ScrStr *scr_bool_to_scrstr(bool b); /* interned "true"/"false" */
@@ -3927,6 +3933,18 @@ bool scr_num_is_safe_integer(double x);
ScrStr *scr_num_to_exponential(double x);
ScrStr *scr_num_to_fixed0(double x);
/* Object.is over two numbers — the spec's SameValue on doubles: NaN
* equals NaN, +0 differs from -0, everything else is ==. Never throws. */
bool scr_num_same_value(double a, double b);
/* Intl.NumberFormat("en-US").format(x) / x.toLocaleString("en-US") with
* default options: decimal notation, 0–3 fraction digits rounded half-up
* on the shortest round-tripping decimal (ICU's rounding input — NOT
* toFixed's exact-value rounding), "," grouping every three integer
* digits, "∞"/"NaN" texts, "-0" for negative inputs rounding to zero.
* en-US is the one embedded locale. Result +1; never throws. */
ScrStr *scr_intl_num_format_en_us(double x);
/* ── Date, the composed slice (scr_lib.c) ─────────────────────────────
* Date values have no representation; the runtime surface is exactly
* Date.now() and toISOString over a millisecond time value. */
@@ -4081,6 +4099,14 @@ typedef enum ScrDataViewGet {
} ScrDataViewGet;
double scr_dataview_get(const ScrBytes *b, double byte_off, ScrDataViewGet kind, bool le);
/* DataView setters (the integer/float kinds only — the BIG kinds never
* lower: bigint arguments have no representation). Values coerce
* JS-exactly: the integer kinds by modular truncation (ToUint32's residue
* — the narrower widths store its low bytes, the same 2^width residue),
* F32 by double→float round-to-nearest-even. Offsets go through ToIndex
* with the getters' one RangeError. */
void scr_dataview_set(ScrBytes *b, double byte_off, double value, ScrDataViewGet kind, bool le);
/* Element read/write. Any invalid index — negative, fractional, NaN, or
* out of bounds — TRAPS like the array runtime (SEMANTICS.md documents
* the divergence from JS's undefined-read/ignored-write). Writes coerce
@@ -0,0 +1,100 @@
// Fresh-ArrayBuffer erasure and DataView setters. `new T(new
// ArrayBuffer(n))` and `new DataView(new ArrayBuffer(n), ...)` erase the
// buffer into the view (nothing else can reference it, so aliasing is
// unobservable); the setter family mirrors the getters: JS-exact value
// coercions (modular truncation, double→float rounding), the optional
// littleEndian flag, and Node's one constant RangeError on bad offsets.
// Erasure into every typed-array kind: zero-filled, correct element count.
const u8 = new Uint8Array(new ArrayBuffer(8));
u8[0] = 255;
console.log(u8.length, u8.byteLength, u8[0], u8[7]);
const u32 = new Uint32Array(new ArrayBuffer(16));
u32[3] = 0xdeadbeef;
console.log(u32.length, u32.byteLength, u32[3]);
const i32 = new Int32Array(new ArrayBuffer(8));
i32[1] = -2;
console.log(i32.length, i32[1]);
const f32 = new Float32Array(new ArrayBuffer(12));
f32[2] = 1.5;
console.log(f32.length, f32[2]);
const empty = new Uint8Array(new ArrayBuffer(0));
console.log(empty.length, empty.byteLength);
// DataView over a fresh buffer: zero bytes until written.
const dv = new DataView(new ArrayBuffer(8));
console.log(dv.byteLength, dv.byteOffset, dv.getFloat64(0));
// Setter/getter roundtrips on every width, both endiannesses.
dv.setUint8(0, 0xab);
dv.setInt8(1, -1);
console.log(dv.getUint8(0), dv.getInt8(1), dv.getUint8(1));
dv.setUint16(2, 0xbeef);
console.log(dv.getUint16(2), dv.getUint8(2), dv.getUint8(3));
dv.setUint16(2, 0xbeef, true);
console.log(dv.getUint16(2, true), dv.getUint8(2), dv.getUint8(3));
dv.setInt16(4, -2, false);
console.log(dv.getInt16(4), dv.getUint16(4));
dv.setUint32(4, 0x01020304);
console.log(dv.getUint32(4), dv.getInt32(4), dv.getUint8(7));
dv.setInt32(4, -16, true);
console.log(dv.getInt32(4, true), dv.getUint32(4, true), dv.getUint32(4));
dv.setFloat64(0, 1.5);
console.log(dv.getFloat64(0), dv.getUint8(0), dv.getUint8(1));
dv.setFloat64(0, 1.5, true);
console.log(dv.getFloat64(0, true), dv.getUint8(6), dv.getUint8(7));
dv.setFloat32(4, 1.1);
console.log(dv.getFloat32(4), dv.getFloat64(0, true));
// Value coercions: modular truncation on the integer kinds (NaN and
// ±Infinity store 0), double→float rounding on Float32.
dv.setUint8(0, 257.9);
dv.setUint8(1, -1);
dv.setUint8(2, 0 / 0);
dv.setUint8(3, 1 / 0);
console.log(dv.getUint8(0), dv.getUint8(1), dv.getUint8(2), dv.getUint8(3));
dv.setUint16(0, 65536 + 7);
console.log(dv.getUint16(0));
dv.setInt16(0, -32769);
console.log(dv.getInt16(0));
dv.setUint32(0, 4294967296 + 41);
console.log(dv.getUint32(0));
dv.setFloat32(0, 0.1);
console.log(dv.getFloat32(0));
// Fractional and NaN offsets go through ToIndex like the getters.
dv.setUint8(5.7, 9);
console.log(dv.getUint8(5));
dv.setUint8(0 / 0, 42);
console.log(dv.getUint8(0));
// The offset/length constructor args keep their runtime story, and a
// view over x.buffer aliases x — setters write through.
const backing = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
const win = new DataView(backing.buffer, 2, 4);
win.setUint16(0, 0x0910);
console.log(win.byteLength, win.byteOffset, backing[2], backing[3], backing[4]);
const dvo = new DataView(new ArrayBuffer(8), 6);
console.log(dvo.byteLength, dvo.byteOffset);
// Bad setter offsets: Node's one constant RangeError, catchably.
function caught(label: string, fn: () => void): void {
try {
fn();
console.log(label, "ok");
} catch (e) {
if (e instanceof RangeError) console.log(label, "RangeError:", e.message);
else console.log(label, "unexpected");
}
}
caught("set-end", () => dv.setUint8(8, 1));
caught("set-neg", () => dv.setUint8(-1, 1));
caught("set-wide", () => dv.setFloat64(1, 1));
caught("set-edge", () => dv.setFloat64(0, 2.5));
caught("set-u32", () => dv.setUint32(5, 1));
caught("set-inf", () => dv.setUint8(1 / 0, 1));
console.log(dv.getFloat64(0));
// Constructor bounds on the fresh-buffer form throw like Node too.
caught("ctor-off", () => { new DataView(new ArrayBuffer(4), 5); });
caught("ctor-len", () => { new DataView(new ArrayBuffer(4), 1, 4); });
+68
View File
@@ -0,0 +1,68 @@
// Object.is — SameValue across the static kinds: the two number
// divergences from === (NaN equals NaN, +0 differs from -0), strings by
// bytes, booleans and units, reference kinds by pointer identity, union
// operands per arm (the number arm's payload compare upgrades to
// SameValue), and statically disjoint kinds answering false with the
// operands still evaluated.
console.log(Object.is(NaN, NaN), Object.is(0 / 0, NaN), Object.is(NaN, 1));
console.log(Object.is(0, -0), Object.is(-0, -0), Object.is(0, 0), Object.is(-0, 0));
console.log(Object.is(1, 1), Object.is(1, 2), Object.is(1.5, 3 / 2), Object.is(1 / 0, 1 / 0), Object.is(1 / 0, -1 / 0));
console.log(Object.is("a", "a"), Object.is("a", "b"), Object.is("", ""), Object.is("ab", "a" + "b"));
console.log(Object.is(true, true), Object.is(true, false), Object.is(false, false));
console.log(Object.is(null, null), Object.is(undefined, undefined), Object.is(null, undefined), Object.is(undefined, null));
// Mixed kinds: always false, and JS still evaluates both arguments.
let hits = 0;
function n(): number { hits++; return 0; }
function s(): string { hits++; return "0"; }
console.log(Object.is(n(), s()), hits);
console.log(Object.is(1, "1"), Object.is(false, 0), Object.is("", 0), Object.is(null, 0), Object.is(undefined, ""));
// Reference identity: arrays, records, functions, Maps/Sets, class
// instances (including through a base-typed slot), and class values.
const arr = [1, 2];
const arr2 = [1, 2];
console.log(Object.is(arr, arr), Object.is(arr, arr2));
const o = { x: 1 };
console.log(Object.is(o, o), Object.is(o, { x: 1 }));
const f = (): number => 1;
const g = (): number => 2;
console.log(Object.is(f, f), Object.is(f, g));
const m = new Map<string, number>();
console.log(Object.is(m, m), Object.is(m, new Map<string, number>()));
const st = new Set<number>([1]);
console.log(Object.is(st, st), Object.is(st, new Set<number>([1])));
class Base { tag = "b"; }
class Derived extends Base { extra = 1; }
const d = new Derived();
const asBase: Base = d;
const otherBase = new Base();
console.log(Object.is(d, d), Object.is(asBase, d), Object.is(asBase, otherBase));
console.log(Object.is(Base, Base), Object.is(Base, Derived));
// Unions: SameValue per arm — NaN and ±0 payloads answer the spec, ref
// arms compare identity, and a plain side wraps like ===.
function u(v: number | string | null): number | string | null { return v; }
const a1 = u(NaN);
const b1 = u(NaN);
console.log(Object.is(a1, b1), a1 === b1);
const z1 = u(0);
const z2 = u(-0);
console.log(Object.is(z1, z2), z1 === z2, Object.is(z2, u(-0)));
console.log(Object.is(u("hi"), u("hi")), Object.is(u("hi"), u(7)), Object.is(u(null), u(null)), Object.is(u(null), u(0)));
console.log(Object.is(u(3), 3), Object.is(3, u(3)), Object.is(u("x"), "x"), Object.is(u(null), null), Object.is(u(3), 4));
console.log(Object.is(u(NaN), NaN), Object.is(u(0), -0), Object.is(u(-0), -0));
// A primitive the union cannot hold: constant false.
console.log(Object.is(u(3), true));
const ru: number[] | null = arr;
console.log(Object.is(ru, arr), Object.is(ru, null));
// Object.is drives control flow like any boolean.
const vals = [0, -0, NaN, 1];
let negZeros = 0;
let nans = 0;
for (const v of vals) {
if (Object.is(v, -0)) negZeros++;
if (Object.is(v, NaN)) nans++;
}
console.log(negZeros, nans);
@@ -0,0 +1,41 @@
// The composed en-US Intl.NumberFormat forms with default options —
// new Intl.NumberFormat("en-US").format(x), the callable spelling, and
// x.toLocaleString("en-US"). Rounding is half-up on the SHORTEST
// round-tripping decimal (ICU's rounding input): format(1.0005) is
// "1.001" although toFixed(3) answers "1.000" on the same double, and
// 1e23 prints the shortest form's zeros, not the double's exact
// expansion. Grouping, ±0, NaN/∞ texts, and the tiny/huge edges pin
// byte-identically against Node.
const nf = (x: number): string => new Intl.NumberFormat("en-US").format(x);
// Grouping and plain decimals.
console.log(nf(0), nf(1), nf(100), nf(1000), nf(10000), nf(999999), nf(-1000000));
console.log(nf(1234567.891), nf(-1234567.891), nf(123456789.9999), nf(0.5), nf(-0.4));
// The shortest-decimal rounding discriminators — (1.0005).toFixed(3)
// would answer "1.000" (exact-value rounding); format answers "1.001".
console.log(nf(1.0005), nf(1.0015), nf(123.4565), nf(7.995));
console.log(nf(0.0625), nf(999.9995), nf(0.1 + 0.2), nf(1.100000023841858));
// Signs, zeros, and the non-finite texts.
console.log(nf(-0), nf(NaN), nf(1 / 0), nf(-1 / 0), nf(-2.5));
// Tiny values collapse to ±0 or the smallest kept fraction digit.
console.log(nf(5e-4), nf(-5e-4), nf(0.00049), nf(1e-7), nf(-1e-7), nf(5e-324));
// Huge values keep full integer digits, grouped — the shortest form's
// zeros for 1e23 (the exact expansion would end ...611,392).
console.log(nf(1e21), nf(1e23), nf(9007199254740993));
console.log(nf(1.7976931348623157e308));
// The callable spelling constructs the same formatter.
console.log(Intl.NumberFormat("en-US").format(12345.678));
// Number.prototype.toLocaleString("en-US") is the same operation.
console.log((1234567.891).toLocaleString("en-US"), (0.0625).toLocaleString("en-US"), (-0).toLocaleString("en-US"));
const xs = [1234.5, -9876543.21, 0.5, 1e6];
console.log(xs.map((x) => x.toLocaleString("en-US")).join("|"));
// Composed results are ordinary strings.
const label = `total: ${nf(1234.5)} units`;
console.log(label, label.length, nf(42).padStart(8, "."));
+4
View File
@@ -10,6 +10,10 @@ const ab = new ArrayBuffer(8, { maxByteLength: 16 });
ab.resize(16);
const moved = ab.transfer();
// The resizable form fences by name even in an erasure position (the
// fixed-length new Uint8Array(new ArrayBuffer(8)) lowers).
const rz = new Uint8Array(new ArrayBuffer(8, { maxByteLength: 16 }));
// SharedArrayBuffer growth.
const sab = new SharedArrayBuffer(8, { maxByteLength: 16 });
sab.grow(16);
+25
View File
@@ -0,0 +1,25 @@
// The Intl surface. What lowers: the COMPOSED en-US NumberFormat forms
// with default options — new Intl.NumberFormat("en-US").format(x) and
// x.toLocaleString("en-US"). Everything else fences by NAME: the
// environment-default locale a compiled binary cannot carry, other
// locales (ICU data the binary does not embed), options bags, formatter
// VALUES, and the rest of the Intl namespace (DurationFormat,
// DateTimeFormat, PluralRules, getCanonicalLocales).
// The unlowered NumberFormat variants, each with its own reason.
const noLocale = new Intl.NumberFormat().format(1234.5);
const otherLocale = new Intl.NumberFormat("de-DE").format(1234.5);
const withOptions = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format(1234.5);
const hoisted = new Intl.NumberFormat("en-US");
const viaValue = hoisted.format(1234.5);
// toLocaleString's unlowered variants.
const envDefault = (1234.5).toLocaleString();
const german = (1234.5).toLocaleString("de-DE");
const grouped = (1234.5).toLocaleString("en-US", { useGrouping: false });
// The rest of Intl keeps the ICU story.
const when = new Intl.DateTimeFormat("en-US").format();
const dur = new Intl.DurationFormat("en-US").format({ hours: 1, minutes: 23 });
const rule = new Intl.PluralRules("en-US").select(1);
const canon = Intl.getCanonicalLocales("en-us");
+9
View File
@@ -67,4 +67,13 @@ const thened = work().then((v) => v + 1, () => 0);
const caught = work().catch(() => 0);
const resolved = Promise.resolve(1);
// Object.is lowers over one comparable kind; the unlowered forms fence
// by name — a validate-first dynamic operand and partially-overlapping
// unions needing narrowing.
const dynIs = Object.is(JSON.parse("1") as unknown, 1);
function pick(a: string | number, b: number | boolean): boolean {
return Object.is(a, b); // partially-overlapping unions: narrow first
}
const picked = pick(1, 2);
const big = 10n;
+4 -4
View File
@@ -5,7 +5,7 @@ dataview.ts:9:7 - error SC2020: 'ArrayBuffer' is part of the standard library ty
| ^~~~~~~
10 |
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: no free-standing ArrayBuffer value exists — typed arrays own their storage (new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view)
dataview.ts:9:17 - error SC1090: '.buffer' outside new DataView(x.buffer, ...) — scriptc typed arrays own their storage: no free-standing ArrayBuffer value exists, and slice()/subarray() copy is not supported yet
@@ -37,7 +37,7 @@ dataview.ts:15:7 - error SC2020: 'ArrayBuffer' is part of the standard library t
| ^~
16 | const direct = new DataView(ab);
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: no free-standing ArrayBuffer value exists — typed arrays own their storage (new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view)
dataview.ts:15:12 - error SC2020: 'new ArrayBuffer' is part of the standard library types but has no scriptc lowering yet
@@ -46,7 +46,7 @@ dataview.ts:15:12 - error SC2020: 'new ArrayBuffer' is part of the standard libr
| ^~~~~~~~~~~~~~~~~~
16 | const direct = new DataView(ab);
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: no free-standing ArrayBuffer value exists — typed arrays own their storage: allocate the view directly (new Uint8Array(n)), or erase a fresh buffer into one (new Uint8Array(new ArrayBuffer(n)), new DataView(new ArrayBuffer(n), ...))
dataview.ts:16:29 - error SC2020: 'new DataView over this buffer expression' is part of the standard library types but has no scriptc lowering yet
@@ -55,4 +55,4 @@ dataview.ts:16:29 - error SC2020: 'new DataView over this buffer expression' is
| ^~
17 |
hint: views compile over a typed array's own storage — new DataView(x.buffer, byteOffset?, byteLength?) where x is a Uint8Array/Uint32Array/Float32Array/Buffer value; free-standing ArrayBuffers have no representation
hint: views compile over a typed array's own storage — new DataView(x.buffer, byteOffset?, byteLength?) where x is a Uint8Array/Uint32Array/Float32Array/Buffer value — or a fresh buffer erased into the view: new DataView(new ArrayBuffer(n), ...); free-standing ArrayBuffers have no representation
@@ -5,7 +5,7 @@ es2025-fences.ts:9:7 - error SC2020: 'ArrayBuffer' is part of the standard libra
| ^~
10 | ab.resize(16);
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: no free-standing ArrayBuffer value exists — typed arrays own their storage (new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view)
es2025-fences.ts:9:12 - error SC2020: 'new ArrayBuffer' is part of the standard library types but has no scriptc lowering yet
@@ -14,7 +14,7 @@ es2025-fences.ts:9:12 - error SC2020: 'new ArrayBuffer' is part of the standard
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10 | ab.resize(16);
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: no free-standing ArrayBuffer value exists — typed arrays own their storage: allocate the view directly (new Uint8Array(n)), or erase a fresh buffer into one (new Uint8Array(new ArrayBuffer(n)), new DataView(new ArrayBuffer(n), ...))
es2025-fences.ts:10:1 - error SC2020: 'ArrayBuffer.resize' is part of the standard library types but has no scriptc lowering yet
@@ -23,7 +23,7 @@ es2025-fences.ts:10:1 - error SC2020: 'ArrayBuffer.resize' is part of the standa
| ^~~~~~~~~
11 | const moved = ab.transfer();
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: resize/transfer/maxByteLength need the buffer to exist as a runtime value, and no free-standing ArrayBuffer value does — typed arrays own fixed-length storage (new Uint8Array(n)); allocate a new view and copy instead
es2025-fences.ts:11:7 - error SC2020: 'ArrayBuffer' is part of the standard library types but has no scriptc lowering yet
@@ -32,7 +32,7 @@ es2025-fences.ts:11:7 - error SC2020: 'ArrayBuffer' is part of the standard libr
| ^~~~~
12 |
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: no free-standing ArrayBuffer value exists — typed arrays own their storage (new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view)
es2025-fences.ts:11:15 - error SC2020: 'ArrayBuffer.transfer' is part of the standard library types but has no scriptc lowering yet
@@ -41,130 +41,139 @@ es2025-fences.ts:11:15 - error SC2020: 'ArrayBuffer.transfer' is part of the sta
| ^~~~~~~~~~~
12 |
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: resize/transfer/maxByteLength need the buffer to exist as a runtime value, and no free-standing ArrayBuffer value does — typed arrays own fixed-length storage (new Uint8Array(n)); allocate a new view and copy instead
es2025-fences.ts:14:7 - error SC2020: 'SharedArrayBuffer' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:15:27 - error SC2020: 'new Uint8Array over a resizable ArrayBuffer' is part of the standard library types but has no scriptc lowering yet
13 | // SharedArrayBuffer growth.
14 | const sab = new SharedArrayBuffer(8, { maxByteLength: 16 });
14 | // fixed-length new Uint8Array(new ArrayBuffer(8)) lowers).
15 | const rz = new Uint8Array(new ArrayBuffer(8, { maxByteLength: 16 }));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 |
hint: a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here erases into the view): drop the options bag
es2025-fences.ts:18:7 - error SC2020: 'SharedArrayBuffer' is part of the standard library types but has no scriptc lowering yet
17 | // SharedArrayBuffer growth.
18 | const sab = new SharedArrayBuffer(8, { maxByteLength: 16 });
| ^~~
15 | sab.grow(16);
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
es2025-fences.ts:14:13 - error SC2020: 'new SharedArrayBuffer' is part of the standard library types but has no scriptc lowering yet
13 | // SharedArrayBuffer growth.
14 | const sab = new SharedArrayBuffer(8, { maxByteLength: 16 });
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
15 | sab.grow(16);
19 | sab.grow(16);
hint: no shared-memory threads exist in a compiled program — Uint8Array is the byte storage
es2025-fences.ts:15:1 - error SC2020: 'SharedArrayBuffer.grow' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:18:13 - error SC2020: 'new SharedArrayBuffer' is part of the standard library types but has no scriptc lowering yet
14 | const sab = new SharedArrayBuffer(8, { maxByteLength: 16 });
15 | sab.grow(16);
17 | // SharedArrayBuffer growth.
18 | const sab = new SharedArrayBuffer(8, { maxByteLength: 16 });
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
19 | sab.grow(16);
hint: no shared-memory threads exist in a compiled program — Uint8Array is the byte storage
es2025-fences.ts:19:1 - error SC2020: 'SharedArrayBuffer.grow' is part of the standard library types but has no scriptc lowering yet
18 | const sab = new SharedArrayBuffer(8, { maxByteLength: 16 });
19 | sab.grow(16);
| ^~~~~~~~
16 |
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
es2025-fences.ts:19:7 - error SC2009: values of type '{ async: false; value: "not-equal" | "timed-out"; } | { async: true; value: Promise<"ok" | "timed-out">; }' cannot be compiled: the union shape is supported, but its arm '{ async: false; value: "not-equal" | "timed-out"; }' does not compile
18 | const ia = new Int32Array(4);
19 | const waited = Atomics.waitAsync(ia, 0, 0, 1);
| ^~~~~~
20 |
hint: no shared-memory threads exist in a compiled program — Uint8Array is the byte storage (a fixed-length allocation: grow has nothing to share it with)
es2025-fences.ts:23:7 - error SC2009: values of type '{ async: false; value: "not-equal" | "timed-out"; } | { async: true; value: Promise<"ok" | "timed-out">; }' cannot be compiled: the union shape is supported, but its arm '{ async: false; value: "not-equal" | "timed-out"; }' does not compile
22 | const ia = new Int32Array(4);
23 | const waited = Atomics.waitAsync(ia, 0, 0, 1);
| ^~~~~~
24 |
hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown')
es2025-fences.ts:19:16 - error SC2020: 'Atomics.waitAsync' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:23:16 - error SC2020: 'Atomics.waitAsync' is part of the standard library types but has no scriptc lowering yet
18 | const ia = new Int32Array(4);
19 | const waited = Atomics.waitAsync(ia, 0, 0, 1);
22 | const ia = new Int32Array(4);
23 | const waited = Atomics.waitAsync(ia, 0, 0, 1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
20 |
24 |
hint: Atomics.wait(int32Array, idx, expected, timeoutMs) is the supported Atomics surface (scriptc has no threads — wait is the synchronous-sleep idiom, and nothing else has anyone to race)
es2025-fences.ts:22:7 - error SC2020: 'Float16Array<ArrayBuffer>' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:26:7 - error SC2020: 'Float16Array<ArrayBuffer>' is part of the standard library types but has no scriptc lowering yet
21 | // Float16 (ES2025): the typed array, Math.f16round, DataView accessors.
22 | const f16 = new Float16Array(4);
25 | // Float16 (ES2025): the typed array, Math.f16round, DataView accessors.
26 | const f16 = new Float16Array(4);
| ^~~
23 | console.log(Math.f16round(1.337));
27 | console.log(Math.f16round(1.337));
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
es2025-fences.ts:22:13 - error SC2020: 'new Float16Array' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:26:13 - error SC2020: 'new Float16Array' is part of the standard library types but has no scriptc lowering yet
21 | // Float16 (ES2025): the typed array, Math.f16round, DataView accessors.
22 | const f16 = new Float16Array(4);
25 | // Float16 (ES2025): the typed array, Math.f16round, DataView accessors.
26 | const f16 = new Float16Array(4);
| ^~~~~~~~~~~~~~~~~~~
23 | console.log(Math.f16round(1.337));
27 | console.log(Math.f16round(1.337));
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
es2025-fences.ts:23:13 - error SC2020: 'Math.f16round' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:27:13 - error SC2020: 'Math.f16round' is part of the standard library types but has no scriptc lowering yet
22 | const f16 = new Float16Array(4);
23 | console.log(Math.f16round(1.337));
26 | const f16 = new Float16Array(4);
27 | console.log(Math.f16round(1.337));
| ^~~~~~~~~~~~~
24 | const dv = new DataView(new Uint8Array(8).buffer);
28 | const dv = new DataView(new Uint8Array(8).buffer);
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
es2025-fences.ts:25:1 - error SC2020: 'DataView<ArrayBuffer>.setFloat16' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:29:1 - error SC2020: 'DataView<ArrayBuffer>.setFloat16' is part of the standard library types but has no scriptc lowering yet
24 | const dv = new DataView(new Uint8Array(8).buffer);
25 | dv.setFloat16(0, 1.5);
28 | const dv = new DataView(new Uint8Array(8).buffer);
29 | dv.setFloat16(0, 1.5);
| ^~~~~~~~~~~~~
26 |
30 |
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
es2025-fences.ts:28:7 - error SC2020: 'IteratorObject<number, undefined, unknown>' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:32:7 - error SC2020: 'IteratorObject<number, undefined, unknown>' is part of the standard library types but has no scriptc lowering yet
27 | // First-class iterator objects.
28 | const it = Iterator.from([1, 2, 3].values());
31 | // First-class iterator objects.
32 | const it = Iterator.from([1, 2, 3].values());
| ^~
29 |
33 |
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
es2025-fences.ts:28:12 - error SC2020: 'Iterator.from' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:32:12 - error SC2020: 'Iterator.from' is part of the standard library types but has no scriptc lowering yet
27 | // First-class iterator objects.
28 | const it = Iterator.from([1, 2, 3].values());
31 | // First-class iterator objects.
32 | const it = Iterator.from([1, 2, 3].values());
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29 |
33 |
hint: first-class iterator objects have no lowering — iterator helpers compile as one chain on an array iterator, consumed in place: arr.values().map(f).take(n).toArray()
es2025-fences.ts:31:15 - error SC2020: 'Promise.try with 3 arguments' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:35:15 - error SC2020: 'Promise.try with 3 arguments' is part of the standard library types but has no scriptc lowering yet
30 | // Promise.try's ...args form (close over the values instead).
31 | const tried = Promise.try((a: number, b: number) => a + b, 1, 2);
34 | // Promise.try's ...args form (close over the values instead).
35 | const tried = Promise.try((a: number, b: number) => a + b, 1, 2);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32 |
36 |
hint: the ...args form has no lowering — close over the values: Promise.try(() => f(a, b))
es2025-fences.ts:34:17 - error SC2020: 'Object.groupBy at this result type' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:38:17 - error SC2020: 'Object.groupBy at this result type' is part of the standard library types but has no scriptc lowering yet
33 | // Object.groupBy at a literal-union key type (a fixed-field record).
34 | const grouped = Object.groupBy([1, 2, 3], (n) => (n % 2 === 0 ? "even" : "odd"));
37 | // Object.groupBy at a literal-union key type (a fixed-field record).
38 | const grouped = Object.groupBy([1, 2, 3], (n) => (n % 2 === 0 ? "even" : "odd"));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35 |
39 |
hint: a string-keyed result (Partial<Record<string, T[]>>) is the supported form — a literal-union key type makes a fixed-field record: widen the callback's key type to string (numbers stringify like JS property keys)
es2025-fences.ts:39:19 - error SC2020: 'Set.prototype.union over a set-like argument of type 'Map<number, string>'' is part of the standard library types but has no scriptc lowering yet
es2025-fences.ts:43:19 - error SC2020: 'Set.prototype.union over a set-like argument of type 'Map<number, string>'' is part of the standard library types but has no scriptc lowering yet
38 | const m = new Map<number, string>([[1, "x"]]);
39 | const u = s.union(m);
42 | const m = new Map<number, string>([[1, "x"]]);
43 | const u = s.union(m);
| ^
40 |
44 |
hint: only a real Set lowers (the argument's has/size must be the builtins) — build one first: new Set(...)
+114
View File
@@ -0,0 +1,114 @@
intl.ts:10:18 - error SC2020: 'Intl.NumberFormat without a locale' is part of the standard library types but has no scriptc lowering yet
9 | // The unlowered NumberFormat variants, each with its own reason.
10 | const noLocale = new Intl.NumberFormat().format(1234.5);
| ^~~~~~~~~~~~~~~~~~~~~~~
11 | const otherLocale = new Intl.NumberFormat("de-DE").format(1234.5);
hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: new Intl.NumberFormat("en-US").format(x)
intl.ts:11:43 - error SC2020: 'Intl.NumberFormat at locale "de-DE"' is part of the standard library types but has no scriptc lowering yet
10 | const noLocale = new Intl.NumberFormat().format(1234.5);
11 | const otherLocale = new Intl.NumberFormat("de-DE").format(1234.5);
| ^~~~~~~
12 | const withOptions = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format(1234.5);
hint: "en-US" (Node's default-build locale) is the one locale whose data the runtime embeds — everything else is ICU data the binary does not carry
intl.ts:12:52 - error SC2020: 'Intl.NumberFormat with an options bag' is part of the standard library types but has no scriptc lowering yet
11 | const otherLocale = new Intl.NumberFormat("de-DE").format(1234.5);
12 | const withOptions = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format(1234.5);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
13 | const hoisted = new Intl.NumberFormat("en-US");
hint: the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction digits, grouping) — new Intl.NumberFormat("en-US").format(x)
intl.ts:13:7 - error SC2020: '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: 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
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);
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");
14 | const viaValue = hoisted.format(1234.5);
| ^~~~~~~~~~~~~~
15 |
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; format with template literals, toFixed, and toString otherwise
intl.ts:17:20 - error SC2020: 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet
16 | // toLocaleString's unlowered variants.
17 | const envDefault = (1234.5).toLocaleString();
| ^~~~~~~~~~~~~~~~~~~~~~~~~
18 | const german = (1234.5).toLocaleString("de-DE");
hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: x.toLocaleString("en-US")
intl.ts:18:40 - error SC2020: 'Number.prototype.toLocaleString at locale "de-DE"' is part of the standard library types but has no scriptc lowering yet
17 | const envDefault = (1234.5).toLocaleString();
18 | const german = (1234.5).toLocaleString("de-DE");
| ^~~~~~~
19 | const grouped = (1234.5).toLocaleString("en-US", { useGrouping: false });
hint: "en-US" (Node's default-build locale) is the one locale whose data the runtime embeds — everything else is ICU data the binary does not carry
intl.ts:19:17 - error SC2020: 'Number.prototype.toLocaleString with an options bag' is part of the standard library types but has no scriptc lowering yet
18 | const german = (1234.5).toLocaleString("de-DE");
19 | const grouped = (1234.5).toLocaleString("en-US", { useGrouping: false });
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
20 |
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
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; format with template literals, toFixed, and toString otherwise
intl.ts:23:13 - error SC2020: 'DurationFormat.format' 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; format with template literals, toFixed, and toString otherwise
intl.ts:24:14 - error SC2020: 'PluralRules.select' 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; format with template literals, toFixed, and toString otherwise
intl.ts:25:15 - error SC2020: 'Intl.getCanonicalLocales' is part of the standard library types but has no scriptc lowering yet
24 | const rule = new Intl.PluralRules("en-US").select(1);
25 | const canon = Intl.getCanonicalLocales("en-us");
| ^~~~~~~~~~~~~~~~~~~~~~~~
26 |
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; format with template literals, toFixed, and toString otherwise
+32 -14
View File
@@ -48,7 +48,7 @@ stdlib-fence.ts:20:7 - error SC2020: 'NumberFormat' is part of the standard libr
| ^~~
21 |
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
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
@@ -109,7 +109,7 @@ stdlib-fence.ts:26:7 - error SC2020: 'ArrayBuffer' is part of the standard libra
| ^~~
27 | const st = new Set(new Set([1, 2])); // array seeds lower; Set/iterable seeds stay fenced
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: no free-standing ArrayBuffer value exists — typed arrays own their storage (new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view)
stdlib-fence.ts:26:13 - error SC2020: 'new ArrayBuffer' is part of the standard library types but has no scriptc lowering yet
@@ -118,7 +118,7 @@ stdlib-fence.ts:26:13 - error SC2020: 'new ArrayBuffer' is part of the standard
| ^~~~~~~~~~~~~~~~~~
27 | const st = new Set(new Set([1, 2])); // array seeds lower; Set/iterable seeds stay fenced
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: no free-standing ArrayBuffer value exists — typed arrays own their storage: allocate the view directly (new Uint8Array(n)), or erase a fresh buffer into one (new Uint8Array(new ArrayBuffer(n)), new DataView(new ArrayBuffer(n), ...))
stdlib-fence.ts:27:12 - error SC2020: 'new Set(values)' is part of the standard library types but has no scriptc lowering yet
@@ -210,14 +210,14 @@ stdlib-fence.ts:42:17 - error SC2020: '.split with 2 arguments on strings' is pa
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
stdlib-fence.ts:44:19 - error SC2020: 'number.toLocaleString' is part of the standard library types but has no scriptc lowering yet
stdlib-fence.ts:44:19 - error SC2020: 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet
43 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer)
44 | const localized = (1234.5).toLocaleString();
| ^~~~~~~~~~~~~~~~~~~~~~~
| ^~~~~~~~~~~~~~~~~~~~~~~~~
45 |
hint: locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data) — format with template literals, toFixed, and toString instead
hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: x.toLocaleString("en-US")
stdlib-fence.ts:49:15 - error SC2020: '.indexOf with 2 arguments' is part of the standard library types but has no scriptc lowering yet
@@ -282,16 +282,34 @@ stdlib-fence.ts:66:16 - error SC2020: 'then with 2 arguments' is part of the sta
hint: the supported form takes exactly one fulfillment handler — chain .catch(...) for the rejection half
stdlib-fence.ts:70:7 - error SC2001: values of type '10n' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown')
stdlib-fence.ts:73:15 - error SC2020: 'Object.is over a dynamic operand' is part of the standard library types but has no scriptc lowering yet
69 |
70 | const big = 10n;
72 | // unions needing narrowing.
73 | const dynIs = Object.is(JSON.parse("1") as unknown, 1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
74 | function pick(a: string | number, b: number | boolean): boolean {
hint: validate/narrow the value first (strict equality's rule) — SameValue only differs from === on numbers (NaN, ±0)
stdlib-fence.ts:75:10 - error SC2020: 'Object.is over these union operands' is part of the standard library types but has no scriptc lowering yet
74 | function pick(a: string | number, b: number | boolean): boolean {
75 | return Object.is(a, b); // partially-overlapping unions: narrow first
| ^~~~~~~~~~~~~~~
76 | }
hint: union-typed comparisons need one comparable shape (narrow first: check a discriminant field, or compare with '!== undefined'/'!== null' for unit arms)
stdlib-fence.ts:79:7 - error SC2001: values of type '10n' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown')
78 |
79 | const big = 10n;
| ^~~
71 |
80 |
stdlib-fence.ts:70:13 - error SC1090: syntax 'BigIntLiteral' is not supported yet
stdlib-fence.ts:79:13 - error SC1090: syntax 'BigIntLiteral' is not supported yet
69 |
70 | const big = 10n;
78 |
79 | const big = 10n;
| ^~~
71 |
80 |