mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Make Dates mutable objects with Node's setters and conversions
This commit is contained in:
@@ -10,6 +10,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea
|
||||
| `atomics-contended` | cpu | four worker threads updating `SharedArrayBuffer` counters with `Atomics`: one shared counter, one counter per worker, a `compareExchange` loop, and `Atomics.wait`/`notify` handoffs |
|
||||
| `channel-messages` | cpu | 200,000 MessageChannel messages in one thread, half numbers and half small objects, all posted before the port delivers any |
|
||||
| `csv-numbers` | cpu | `split`, `Number`, `parseInt`, `toFixed` |
|
||||
| `date-math` | cpu | schedules built with Date setters (local and UTC), weekday and month-end rules through the getters, Dates sorted by time value |
|
||||
| `cyclic-graphs` | cpu | object graphs with back references built and dropped: trees with parent and sibling links, rings of records sharing a long-lived registry, closures capturing their owner |
|
||||
| `dictionary-walks` | cpu | `Record<string, number>` dictionaries walked with for-in and `Object.keys`/`values`/`entries` while entries are added, updated and deleted |
|
||||
| `dynamic-methods` | cpu | untyped JavaScript calling builtin methods on values it does not type: string `slice`/`indexOf`/`toUpperCase`/`padStart`, array `push`/`pop`/`includes`, `Map` and `Set`, and an object's own methods |
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"entry": "workloads/date-math.ts",
|
||||
"args": [
|
||||
"40"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Calendar arithmetic on Dates: schedules built by adjusting Dates with the
|
||||
// UTC and local setters, weekday and month-end rules read back through the
|
||||
// getters, Dates compared and sorted by time value, and ISO text for a
|
||||
// sample. Argument: the number of rounds.
|
||||
process.env.TZ = "America/New_York";
|
||||
|
||||
const rounds = Number(process.argv[2] ?? "40");
|
||||
let seed = 2026;
|
||||
function random(): number {
|
||||
seed = (Math.imul(seed, 1103515245) + 12345) >>> 0;
|
||||
return seed / 4294967296;
|
||||
}
|
||||
|
||||
/** The next business day at 09:00 local time on or after `from`. */
|
||||
function nextBusinessDay(from: Date): Date {
|
||||
const day = new Date(from.getTime());
|
||||
day.setHours(9, 0, 0, 0);
|
||||
if (day < from) day.setDate(day.getDate() + 1);
|
||||
while (day.getDay() === 0 || day.getDay() === 6) day.setDate(day.getDate() + 1);
|
||||
return day;
|
||||
}
|
||||
|
||||
/** The last day of `date`'s month, in UTC. */
|
||||
function monthEnd(date: Date): number {
|
||||
const end = new Date(date.getTime());
|
||||
end.setUTCDate(1);
|
||||
end.setUTCMonth(end.getUTCMonth() + 1);
|
||||
end.setUTCDate(0);
|
||||
return end.getUTCDate();
|
||||
}
|
||||
|
||||
let checksum = 0;
|
||||
let sample = "";
|
||||
for (let round = 0; round < rounds; round++) {
|
||||
const due: Date[] = [];
|
||||
const cursor = new Date(Date.UTC(2020 + (round % 7), 0, 1, 12));
|
||||
for (let i = 0; i < 4000; i++) {
|
||||
cursor.setUTCMinutes(cursor.getUTCMinutes() + Math.floor(random() * 4000));
|
||||
const next = nextBusinessDay(cursor);
|
||||
checksum += next.getDate() + monthEnd(next) + next.getHours();
|
||||
if (i % 3 === 0) next.setMonth(next.getMonth() + 1, 15);
|
||||
due.push(next);
|
||||
}
|
||||
due.sort((a, b) => +a - +b);
|
||||
let previous = due[0]!;
|
||||
for (const date of due) {
|
||||
checksum += (+date - +previous) / 3600000;
|
||||
previous = date;
|
||||
}
|
||||
sample = due[due.length >> 1]!.toISOString();
|
||||
}
|
||||
console.log(Math.round(checksum), sample);
|
||||
@@ -1,6 +1,7 @@
|
||||
- **Date:** Spread constructor arguments, setters, thrown Date values, and locale formatters are unsupported.
|
||||
Dates are objects: `===` compares identity, `new Date(date)` makes a new Date, and Dates compile in arrays (`includes`, `indexOf`, `lastIndexOf`, `join`, and default `sort` by `toString()` text), in `Date | undefined` and `Date | null` slots, as Map and Set keys and values, and in records, tuples, class fields, closures, promises and generators.
|
||||
Unions of a Date with other non-nullish types are unsupported.
|
||||
Zero-argument, single number/string, and local-time year/month field constructors, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, `String(date)` and template literal conversion, relational comparison, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile.
|
||||
- **Date:** Spread constructor arguments and locale formatters are unsupported.
|
||||
Dates are objects with identity: `===` and `==` compare identity, `new Date(date)` makes a new Date, setters change the one Date every reference holds, and Dates compile in arrays (`includes`, `indexOf`, `lastIndexOf`, `join`, and default `sort` by `toString()` text), in unions with other types, as Map and Set keys and values, and in records, tuples, class fields, closures, promises and generators.
|
||||
Thrown and rejected Dates keep their identity; `structuredClone` and `postMessage` copy them.
|
||||
Zero-argument, single number/string, and local-time year/month field constructors, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, the local and UTC calendar getters and setters, `setTime`, `getTimezoneOffset`, and single-string `Date.parse` compile.
|
||||
Unary `+`, `-` and `Number(date)` read the time value; `String(date)`, template literals and `+` with a string read the `toString()` text; `date[Symbol.toPrimitive](hint)` compiles with a literal hint.
|
||||
A Date converted to `unknown` is the same Date object.
|
||||
See [Dates and timezones](#dates-and-timezones) for timezone data.
|
||||
|
||||
@@ -649,6 +649,15 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
if (MAY_THROW_LIB_FNS.has(e.fn) && LIB_FN_SYMS[e.fn] === undefined) {
|
||||
throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc);
|
||||
}
|
||||
// A setter's write: the time value into the Date object (ScrDate.ms).
|
||||
if (e.fn === "date.store") {
|
||||
const date = emitBorrowedInput(host, e.args[0]!).name;
|
||||
const ms = host.emitExpr(e.args[1]!).name;
|
||||
const field = B.tmp();
|
||||
B.line(`${field} = getelementptr inbounds i8, ptr ${date}, i64 8 ; ->ms`);
|
||||
B.line(`store double ${ms}, ptr ${field}`);
|
||||
return { name: ms, type: e.type };
|
||||
}
|
||||
// queueMicrotask's checked-dynamic form carries the AsyncLocalStorage
|
||||
// context like the static one (lib-process.ts).
|
||||
const sym =
|
||||
|
||||
@@ -334,6 +334,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"date.getTime": "scr_date_get_time",
|
||||
"date.valueOf": "scr_date_get_time",
|
||||
"date.fromTime": "scr_date_get_time",
|
||||
"date.setFields": "scr_date_set_fields",
|
||||
"date.timeClip": "scr_date_new_ms",
|
||||
"date.parseGetTime": "scr_date_parse",
|
||||
"date.utc": "scr_date_utc",
|
||||
"date.newLocal": "scr_date_new_local",
|
||||
|
||||
@@ -622,7 +622,7 @@ export function unsupportedTypeDiag(typeText: string, loc: SrcLoc): ScrDiagnosti
|
||||
code: "SC2001",
|
||||
message:
|
||||
`values of type '${typeText}' cannot be compiled yet ` +
|
||||
`(supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')`,
|
||||
`(supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')`,
|
||||
loc,
|
||||
milestone: "M2",
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
DYN,
|
||||
DYN_HANDLE_KINDS,
|
||||
F64,
|
||||
STRING,
|
||||
type IrExpr,
|
||||
type IrStmt,
|
||||
type IrType,
|
||||
@@ -285,6 +286,43 @@ export function lowerAbstractEquality(
|
||||
};
|
||||
return negated ? { kind: "unary", op: "!", operand: result, type: BOOL, loc } : result;
|
||||
}
|
||||
// Two Dates are equal when they are the same object, as with ===.
|
||||
if (left.type.kind === "date" && right.type.kind === "date")
|
||||
return { kind: "bin", op: negated ? "!==" : "===", left, right, type: BOOL, loc };
|
||||
// A Date and a string or number compare through the Date's default
|
||||
// primitive, its toString() text.
|
||||
if (left.type.kind === "date" || right.type.kind === "date") {
|
||||
const otherType = left.type.kind === "date" ? right.type : left.type;
|
||||
if (otherType.kind === "string" || otherType.kind === "f64") {
|
||||
// Both operands evaluate before the Date converts.
|
||||
const l = lowerer.declareHiddenLocal("%looseA", left.type);
|
||||
const r = lowerer.declareHiddenLocal("%looseB", right.type);
|
||||
const stmts: IrStmt[] = [
|
||||
{ kind: "varDecl", localId: l.id, init: left, loc },
|
||||
{ kind: "varDecl", localId: r.id, init: right, loc },
|
||||
];
|
||||
const primitive = (value: IrExpr): IrExpr => {
|
||||
if (value.type.kind !== "date") return value;
|
||||
const text: IrExpr = {
|
||||
kind: "libCall",
|
||||
fn: "date.toString",
|
||||
args: [value],
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
return otherType.kind === "string"
|
||||
? text
|
||||
: { kind: "libCall", fn: "num.fromString", args: [text], type: F64, loc };
|
||||
};
|
||||
const a = primitive(varRef(l.id, left.type, loc));
|
||||
const b = primitive(varRef(r.id, right.type, loc));
|
||||
const result: IrExpr =
|
||||
otherType.kind === "string"
|
||||
? { kind: "strEq", negated, left: a, right: b, type: BOOL, loc }
|
||||
: { kind: "bin", op: negated ? "!==" : "===", left: a, right: b, type: BOOL, loc };
|
||||
return { kind: "seqExpr", stmts, result, type: BOOL, loc };
|
||||
}
|
||||
}
|
||||
if (!supportsPair(lowerer, left.type, right.type)) return null;
|
||||
if (left.kind === "strLit" && right.kind === "strLit") {
|
||||
return { kind: "boolLit", value: (left.value === right.value) !== negated, type: BOOL, loc };
|
||||
|
||||
@@ -2,7 +2,13 @@ import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import { type ClassInfo } from "../lower-classes.js";
|
||||
import { DATE_METHOD_HINT, dateJsonType, dateValueMethod } from "./dates.js";
|
||||
import {
|
||||
DATE_METHOD_HINT,
|
||||
dateJsonType,
|
||||
dateSetterTime,
|
||||
dateValueMethod,
|
||||
isDateSetter,
|
||||
} from "./dates.js";
|
||||
import { dynUndefinedExpr, varRef } from "../../../ir/build.js";
|
||||
import {
|
||||
BOOL,
|
||||
@@ -248,6 +254,20 @@ export function lowerInheritedDateMethodCall(
|
||||
type: result.type,
|
||||
loc,
|
||||
});
|
||||
if (isDateSetter(name) && name !== "setTime") {
|
||||
// The field setters change the Date the class's time slot holds.
|
||||
const date = lowerer.declareHiddenLocal("%dateTime", DATE_T);
|
||||
stmts.push({
|
||||
kind: "varDecl",
|
||||
localId: date.id,
|
||||
init: timeOf(lowerer, value, root, loc),
|
||||
loc,
|
||||
});
|
||||
const current = varRef(date.id, DATE_T, loc);
|
||||
const time: IrExpr = { kind: "libCall", fn: "date.getTime", args: [current], type: F64, loc };
|
||||
const next = dateSetterTime(lowerer, call, name, stmts, time);
|
||||
return done({ kind: "libCall", fn: "date.store", args: [current, next], type: F64, loc });
|
||||
}
|
||||
if (name === "setTime") {
|
||||
// Every argument is evaluated before setTime converts the first one.
|
||||
let ms: IrExpr = { kind: "numLit", value: Number.NaN, type: F64, loc };
|
||||
|
||||
@@ -2,12 +2,15 @@ import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer, own } from "../lowerer.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import { tryLowerExpression } from "../expressions/try-lower-expression.js";
|
||||
import { lowerToNumberArguments } from "../lower-exprs.js";
|
||||
import { programDeclaresDateClass } from "./date-classes.js";
|
||||
import {
|
||||
BOOL,
|
||||
DATE_T,
|
||||
F64,
|
||||
type IrExpr,
|
||||
type IrLibFn,
|
||||
type IrLocal,
|
||||
type IrStmt,
|
||||
type IrType,
|
||||
NULL_T,
|
||||
@@ -36,6 +39,32 @@ const DATE_GETTER_FNS: Readonly<Record<string, IrLibFn>> = {
|
||||
getTimezoneOffset: "date.getTimezoneOffset",
|
||||
};
|
||||
|
||||
/** The setters that change calendar fields: the field code
|
||||
* scr_date_set_fields takes (scr_number.h SCR_DATE_SET_*; 8 marks the UTC
|
||||
* forms) and the most arguments each takes. */
|
||||
const DATE_FIELD_SETTERS: ReadonlyMap<string, { code: number; max: number }> = new Map([
|
||||
["setMilliseconds", { code: 0, max: 1 }],
|
||||
["setSeconds", { code: 1, max: 2 }],
|
||||
["setMinutes", { code: 2, max: 3 }],
|
||||
["setHours", { code: 3, max: 4 }],
|
||||
["setDate", { code: 4, max: 1 }],
|
||||
["setMonth", { code: 5, max: 2 }],
|
||||
["setFullYear", { code: 6, max: 3 }],
|
||||
["setUTCMilliseconds", { code: 8, max: 1 }],
|
||||
["setUTCSeconds", { code: 9, max: 2 }],
|
||||
["setUTCMinutes", { code: 10, max: 3 }],
|
||||
["setUTCHours", { code: 11, max: 4 }],
|
||||
["setUTCDate", { code: 12, max: 1 }],
|
||||
["setUTCMonth", { code: 13, max: 2 }],
|
||||
["setUTCFullYear", { code: 14, max: 3 }],
|
||||
]);
|
||||
|
||||
/** Whether `name` is a Date setter scriptc lowers: setTime or a field
|
||||
* setter. */
|
||||
export function isDateSetter(name: string): boolean {
|
||||
return name === "setTime" || DATE_FIELD_SETTERS.has(name);
|
||||
}
|
||||
|
||||
const DATE_TEXT_FNS: ReadonlyMap<string, IrLibFn> = new Map([
|
||||
["toString", "date.toString"],
|
||||
["toDateString", "date.toDateString"],
|
||||
@@ -43,11 +72,11 @@ const DATE_TEXT_FNS: ReadonlyMap<string, IrLibFn> = new Map([
|
||||
]);
|
||||
|
||||
export const DATE_METHOD_HINT =
|
||||
"getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale formatters have no lowering";
|
||||
"getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters and setters, setTime(), and getTimezoneOffset() are supported; locale formatters have no lowering";
|
||||
|
||||
/** The Date slice: statics plus read-only Date values backed by one
|
||||
* TimeClip'd millisecond scalar. Construction lives in lowerNew;
|
||||
* identity and mutating methods remain fenced. */
|
||||
/** Date statics and methods on Date objects (a TimeClip'd millisecond
|
||||
* time value with identity): getters, text forms and setters.
|
||||
* Construction lives in lowerNew. */
|
||||
export function lowerDateCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
@@ -67,16 +96,18 @@ export function lowerDateCall(
|
||||
if (lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind !== "date") return null;
|
||||
if (!lowerer.isStdlibMember(access)) return null;
|
||||
const name = access.name.text;
|
||||
// A Date a JavaScript file constructs is a mutable object; setTime on
|
||||
// it takes the dynamic method call. So does every Date method on a
|
||||
// A Date a JavaScript file constructs is a checked-dynamic value; a
|
||||
// setter on it takes the dynamic method call, which changes the Date the
|
||||
// handle holds. So does every Date method on a
|
||||
// checked-dynamic receiver when the program declares a class that extends
|
||||
// Date: the value can be an instance, whose class may declare the method.
|
||||
if (
|
||||
(name === "setTime" || programDeclaresDateClass(lowerer)) &&
|
||||
(isDateSetter(name) || programDeclaresDateClass(lowerer)) &&
|
||||
tryLowerExpression(lowerer, access.expression)?.type.kind === "dyn"
|
||||
)
|
||||
return null;
|
||||
if (name === "toJSON") return lowerDateToJson(lowerer, call, access);
|
||||
if (isDateSetter(name)) return lowerDateSetter(lowerer, call, access, name);
|
||||
if (call.arguments.nodes.length !== 0) {
|
||||
lowerer.noLowering(`Date.prototype.${name} with arguments`, call, DATE_METHOD_HINT);
|
||||
}
|
||||
@@ -253,3 +284,150 @@ export function isDateJsonSource(lowerer: Lowerer, node: ts.Node): boolean {
|
||||
lowerer.isStdlibMember(access)
|
||||
);
|
||||
}
|
||||
|
||||
/** `d.setTime(t)` and the field setters (`d.setHours(h, m?, s?, ms?)` ...)
|
||||
* on a Date object: the receiver, then the arguments in order, then the
|
||||
* new time value (scr_date_set_fields over the time value the Date holds
|
||||
* once the arguments have run) stored into the Date and answered. Missing
|
||||
* optional arguments keep their fields; arguments past a setter's last
|
||||
* one are evaluated and ignored. A Date local that holds its time value
|
||||
* (date-values.ts) takes the new time value itself. */
|
||||
function lowerDateSetter(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
name: string,
|
||||
): IrExpr {
|
||||
const loc = locOf(call);
|
||||
const nodes = call.arguments.nodes;
|
||||
if (nodes.some((node) => ts.isSpreadElement(node)))
|
||||
lowerer.noLowering(`Date.prototype.${name} with spread arguments`, call, DATE_METHOD_HINT);
|
||||
const timeLocal = timeValueLocalOf(lowerer, access.expression);
|
||||
const stmts: IrStmt[] = [];
|
||||
let receiver: IrExpr | null = null;
|
||||
if (timeLocal === null) {
|
||||
// A checked-dynamic receiver takes the dynamic method call
|
||||
// (lowerDateCall), so the value here is a Date object.
|
||||
const value = lowerer.lowerExpr(access.expression);
|
||||
if (value.type.kind !== "date")
|
||||
lowerer.badType(access.expression, lowerer.typeOf(access.expression));
|
||||
const date = lowerer.declareHiddenLocal("%date", DATE_T);
|
||||
stmts.push({ kind: "varDecl", localId: date.id, init: value, loc });
|
||||
receiver = { kind: "varRef", localId: date.id, type: DATE_T, loc };
|
||||
}
|
||||
const time: IrExpr =
|
||||
receiver === null
|
||||
? { kind: "varRef", localId: timeLocal!.id, type: F64, loc }
|
||||
: { kind: "libCall", fn: "date.getTime", args: [receiver], type: F64, loc };
|
||||
const next = dateSetterTime(lowerer, call, name, stmts, time);
|
||||
if (receiver === null) {
|
||||
stmts.push({ kind: "assign", localId: timeLocal!.id, value: next, loc });
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts,
|
||||
result: { kind: "varRef", localId: timeLocal!.id, type: F64, loc },
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts,
|
||||
result: { kind: "libCall", fn: "date.store", args: [receiver, next], type: F64, loc },
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** The new time value of setter `name` called with `call`'s arguments on a
|
||||
* Date whose time value `time` reads: the arguments evaluate into
|
||||
* temporaries appended to `stmts` (ToNumber of each, an undefined one is
|
||||
* NaN), and the result reads `time` after them. Missing optional arguments
|
||||
* keep their fields; arguments past a setter's last one are evaluated and
|
||||
* ignored. */
|
||||
export function dateSetterTime(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
name: string,
|
||||
stmts: IrStmt[],
|
||||
time: IrExpr,
|
||||
): IrExpr {
|
||||
const loc = locOf(call);
|
||||
const converted = lowerToNumberArguments(
|
||||
lowerer,
|
||||
call.arguments.nodes as readonly ts.Expression[],
|
||||
);
|
||||
stmts.push(...converted.stmts);
|
||||
const args: IrExpr[] = converted.values.map((value) => {
|
||||
if (value.kind === "numLit") return value;
|
||||
const arg = lowerer.declareHiddenLocal("%dateArg", F64);
|
||||
stmts.push({ kind: "varDecl", localId: arg.id, init: value, loc });
|
||||
return { kind: "varRef", localId: arg.id, type: F64, loc };
|
||||
});
|
||||
const number = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc });
|
||||
if (name === "setTime")
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "date.timeClip",
|
||||
args: [args[0] ?? number(Number.NaN)],
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
const setter = DATE_FIELD_SETTERS.get(name)!;
|
||||
const used = args.slice(0, setter.max);
|
||||
const operands = [...used];
|
||||
while (operands.length < 4) operands.push(number(0));
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "date.setFields",
|
||||
args: [time, number(setter.code), number(used.length), ...operands],
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** The local a Date binding that holds its time value lowers to, for an
|
||||
* identifier `node` that names one (date-values.ts); null otherwise. */
|
||||
function timeValueLocalOf(lowerer: Lowerer, node: ts.Expression): IrLocal | null {
|
||||
while (ts.isParenthesizedExpression(node)) node = node.expression;
|
||||
if (!ts.isIdentifier(node)) return null;
|
||||
const local = lowerer.resolveLocal(node);
|
||||
return local !== undefined && local !== null && lowerer.timeValueDateLocals.has(local)
|
||||
? local
|
||||
: null;
|
||||
}
|
||||
|
||||
/** `d[Symbol.toPrimitive](hint)` on a Date with a literal hint: "number"
|
||||
* answers the time value, "string" and "default" the toString() text (a
|
||||
* Date's default hint is a string). Null for other calls. */
|
||||
export function lowerDateToPrimitiveCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.ElementAccessExpression,
|
||||
): IrExpr | null {
|
||||
const key = access.argumentExpression;
|
||||
if (
|
||||
!ts.isPropertyAccessExpression(key) ||
|
||||
key.name.text !== "toPrimitive" ||
|
||||
!ts.isIdentifier(key.expression) ||
|
||||
!lowerer.isStdlibGlobal(key.expression, "Symbol") ||
|
||||
lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind !== "date"
|
||||
)
|
||||
return null;
|
||||
const loc = locOf(call);
|
||||
const hint = call.arguments.nodes.length === 1 ? call.arguments.nodes[0]! : null;
|
||||
const text = hint !== null && ts.isStringLiteralLike(hint) ? hint.text : null;
|
||||
if (text !== "number" && text !== "string" && text !== "default")
|
||||
return lowerer.noLowering(
|
||||
'Date.prototype[Symbol.toPrimitive] without a "number", "string" or "default" literal hint',
|
||||
call,
|
||||
);
|
||||
const raw = lowerer.lowerExpr(access.expression);
|
||||
const date: IrExpr =
|
||||
raw.type.kind === "dyn"
|
||||
? { kind: "libCall", fn: "date.checkedValue", args: [raw], type: DATE_T, loc }
|
||||
: raw;
|
||||
return text === "number"
|
||||
? { kind: "libCall", fn: "date.valueOf", args: [date], type: F64, loc }
|
||||
: { kind: "libCall", fn: "date.toString", args: [date], type: STRING, loc };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import { isDateSetter } from "./builtins/dates.js";
|
||||
|
||||
/** Date methods whose receiver is only read for its time value: a Date
|
||||
* local used as their receiver can store the time value instead of a Date
|
||||
@@ -41,12 +42,14 @@ const RELATIONAL = new Set([
|
||||
|
||||
/** True when a local `const` (or a `let` that is never assigned again)
|
||||
* initialized with `new Date(...)` can keep the Date's time value instead
|
||||
* of a Date object: no use observes the Date's identity. Every use reads the time value as
|
||||
* a Date method receiver, a String() or template operand, a relational or
|
||||
* string `+` operand, or `new Date(d)`'s argument, inside the declaring
|
||||
* function, or inside the block or loop that scopes a module-level
|
||||
* binding (no closure captures it). Top-level module bindings keep the
|
||||
* object. Other uses box the time value into a new Date at the use. */
|
||||
* of a Date object: no use observes the Date's identity. Every use reads
|
||||
* the time value as a Date method receiver (a setter replaces it), a
|
||||
* String(), Number() or template operand, a unary + or - operand, a
|
||||
* relational or string `+` operand, or `new Date(d)`'s argument, inside
|
||||
* the declaring function, or inside the block or loop that scopes a
|
||||
* module-level binding (no closure captures it). Top-level module bindings
|
||||
* keep the object. Other uses box the time value into a new Date at the
|
||||
* use. */
|
||||
export function isTimeValueDateDeclaration(
|
||||
lowerer: Lowerer,
|
||||
decl: ts.VariableDeclaration,
|
||||
@@ -94,17 +97,21 @@ function decideTimeValueDate(lowerer: Lowerer, decl: ts.VariableDeclaration): bo
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
|
||||
if (!symbol) return false;
|
||||
let ok = true;
|
||||
let sets = false;
|
||||
const visit = (node: ts.Node): void => {
|
||||
if (!ok) return;
|
||||
if (ts.isIdentifier(node) && node !== decl.name && node.text === decl.name.text) {
|
||||
if (lowerer.checker.getSymbolAtLocation(node) === symbol) {
|
||||
if (enclosingFunction(node) !== fn || !readsTimeValue(lowerer, node)) ok = false;
|
||||
const use = enclosingFunction(node) === fn ? timeValueUse(lowerer, node) : null;
|
||||
if (use === null) ok = false;
|
||||
else if (use === "set") sets = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(scope);
|
||||
if (ok && sets) lowerer.timeValueDateWrites.add(decl);
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -164,29 +171,46 @@ function enclosingFunction(node: ts.Node): ts.FunctionLikeDeclaration | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** A use of the binding that reads only its time value. */
|
||||
function readsTimeValue(lowerer: Lowerer, id: ts.Identifier): boolean {
|
||||
/** How a use of the binding uses its time value: "read" for a use that
|
||||
* reads only the time value, "set" for a setter call that replaces it, or
|
||||
* null for any other use. */
|
||||
function timeValueUse(lowerer: Lowerer, id: ts.Identifier): "read" | "set" | null {
|
||||
let node: ts.Node = id;
|
||||
while (ts.isParenthesizedExpression(node.parent)) node = node.parent;
|
||||
const parent = node.parent;
|
||||
if (parent === undefined) return null;
|
||||
if (ts.isPropertyAccessExpression(parent) && parent.expression === node) {
|
||||
const call = parent.parent;
|
||||
return (
|
||||
!parent.questionDotToken &&
|
||||
ts.isCallExpression(call) &&
|
||||
call.expression === parent &&
|
||||
!call.questionDotToken &&
|
||||
TIME_VALUE_METHODS.has(parent.name.text) &&
|
||||
lowerer.isStdlibMember(parent)
|
||||
);
|
||||
if (
|
||||
parent.questionDotToken ||
|
||||
!ts.isCallExpression(call) ||
|
||||
call.expression !== parent ||
|
||||
call.questionDotToken ||
|
||||
!lowerer.isStdlibMember(parent)
|
||||
)
|
||||
return null;
|
||||
if (isDateSetter(parent.name.text)) return "set";
|
||||
return TIME_VALUE_METHODS.has(parent.name.text) ? "read" : null;
|
||||
}
|
||||
return readsTimeValue(lowerer, node, parent) ? "read" : null;
|
||||
}
|
||||
|
||||
/** A use of the binding, other than a method call, that reads only its
|
||||
* time value. */
|
||||
function readsTimeValue(lowerer: Lowerer, node: ts.Node, parent: ts.Node): boolean {
|
||||
if (ts.isTemplateSpan(parent)) return parent.expression === node;
|
||||
// Unary + and -, the number hint.
|
||||
if (ts.isPrefixUnaryExpression(parent))
|
||||
return (
|
||||
parent.operator === ts.SyntaxKind.PlusToken || parent.operator === ts.SyntaxKind.MinusToken
|
||||
);
|
||||
if (ts.isCallExpression(parent)) {
|
||||
return (
|
||||
parent.arguments.nodes.length === 1 &&
|
||||
parent.arguments.nodes[0] === node &&
|
||||
ts.isIdentifier(parent.expression) &&
|
||||
lowerer.isStdlibGlobal(parent.expression, "String")
|
||||
(lowerer.isStdlibGlobal(parent.expression, "String") ||
|
||||
lowerer.isStdlibGlobal(parent.expression, "Number"))
|
||||
);
|
||||
}
|
||||
if (ts.isNewExpression(parent)) {
|
||||
|
||||
@@ -1600,6 +1600,19 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
|
||||
loc,
|
||||
};
|
||||
}
|
||||
// A Date against an untyped value: the same Date object, whose
|
||||
// checked-dynamic handle the untyped value would hold.
|
||||
if (dynSide.type.kind === "dyn" && scalarSide.type.kind === "date") {
|
||||
const boxed = lowerer.coerceToExpected(scalarSide, DYN);
|
||||
return {
|
||||
kind: "dynScalarEq",
|
||||
left: scalarSide === left ? boxed : left,
|
||||
right: scalarSide === right ? boxed : right,
|
||||
...(negated ? { negated: true as const } : {}),
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (
|
||||
dynSide.type.kind === "dyn" &&
|
||||
(scalarSide.type.kind === "f64" ||
|
||||
|
||||
@@ -112,6 +112,7 @@ import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../.
|
||||
import { typedArrayLacksMember } from "../builtins/typed-array-members.js";
|
||||
import { TEMPORAL_GLOBAL } from "../../builtin-modules.js";
|
||||
import { tryLowerExpression } from "./try-lower-expression.js";
|
||||
import { DATE_METHOD_HINT } from "../builtins/dates.js";
|
||||
import {
|
||||
cjsClassExprWholeExportOf,
|
||||
cjsExportAssignmentOf,
|
||||
@@ -2055,6 +2056,9 @@ export function lowerIntrinsicProperty(
|
||||
);
|
||||
}
|
||||
if (kind === "date") {
|
||||
// A Date held as a checked-dynamic value (a Date a JavaScript file
|
||||
// constructs) answers its method values at run time.
|
||||
if (tryLowerExpression(lowerer, expr.expression)?.type.kind === "dyn") return null;
|
||||
const methods = new Set([
|
||||
"getTime",
|
||||
"valueOf",
|
||||
@@ -2083,7 +2087,7 @@ export function lowerIntrinsicProperty(
|
||||
lowerer.noLowering(
|
||||
`Date.prototype.${name}`,
|
||||
expr,
|
||||
"getTime(), valueOf(), toISOString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale/string formatters have no lowering",
|
||||
DATE_METHOD_HINT,
|
||||
lowerer.checker.getSymbolAtLocation(expr.name),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -288,6 +288,16 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc
|
||||
if (!target) {
|
||||
const date = lowerer.isStdlibGlobal(expr.right, "Date");
|
||||
const url = lowerer.isStdlibGlobal(expr.right, "URL");
|
||||
// A catch binding holds a thrown Date as its checked-dynamic handle.
|
||||
const caughtDate = date ? lowerer.caughtLocalOf(expr.left) : null;
|
||||
if (caughtDate)
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "dyn.nativeDateIs",
|
||||
args: [{ kind: "caughtToDyn", value: varRef(caughtDate.id, CAUGHT, loc), type: DYN, loc }],
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
if ((date || url) && !lowerer.caughtLocalOf(expr.left)) {
|
||||
const value = lowerer.lowerExpr(expr.left);
|
||||
if (value.type.kind === "dyn")
|
||||
|
||||
@@ -281,6 +281,7 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc
|
||||
promise: "object",
|
||||
bytes: "object",
|
||||
regex: "object",
|
||||
date: "object",
|
||||
generator: "object",
|
||||
classval: "function",
|
||||
moduleNs: "object",
|
||||
|
||||
@@ -120,6 +120,11 @@ function coercedUnion(lowerer: Lowerer, type: IrType): boolean {
|
||||
return arms.some((arm) => arm.kind !== "f64" && !isUnitType(arm));
|
||||
}
|
||||
|
||||
/** ToNumber of a Date: its time value (valueOf, the number hint). */
|
||||
function dateTimeValue(date: IrExpr, loc: SrcLoc): IrExpr {
|
||||
return { kind: "libCall", fn: "date.valueOf", args: [date], type: F64, loc };
|
||||
}
|
||||
|
||||
/** ToNumber of a boolean: 1 for true, 0 for false. */
|
||||
function booleanToNumber(value: IrExpr, loc: SrcLoc): IrExpr {
|
||||
return {
|
||||
@@ -145,23 +150,25 @@ export function lowerPrefixUnary(lowerer: Lowerer, expr: ts.PrefixUnaryExpressio
|
||||
const operand =
|
||||
raw.type.kind === "bool"
|
||||
? booleanToNumber(raw, loc)
|
||||
: coercedUnion(lowerer, raw.type)
|
||||
? ({
|
||||
kind: "libCall",
|
||||
fn: "dyn.toNumberCoerce",
|
||||
args: [{ kind: "dynFrom", value: raw, type: DYN, loc }],
|
||||
type: F64,
|
||||
loc,
|
||||
} satisfies IrExpr)
|
||||
: raw.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))
|
||||
: raw.type.kind === "date"
|
||||
? dateTimeValue(raw, loc)
|
||||
: coercedUnion(lowerer, raw.type)
|
||||
? ({
|
||||
kind: "libCall",
|
||||
fn: "dyn.toNumberCoerce",
|
||||
args: [raw],
|
||||
args: [{ kind: "dynFrom", value: raw, type: DYN, loc }],
|
||||
type: F64,
|
||||
loc,
|
||||
} satisfies IrExpr)
|
||||
: lowerOptionalNumber(lowerer, raw, loc, expr.operand);
|
||||
: raw.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))
|
||||
? ({
|
||||
kind: "libCall",
|
||||
fn: "dyn.toNumberCoerce",
|
||||
args: [raw],
|
||||
type: F64,
|
||||
loc,
|
||||
} satisfies IrExpr)
|
||||
: lowerOptionalNumber(lowerer, raw, loc, expr.operand);
|
||||
if (operand.type.kind !== "f64") operatorUnsupported(lowerer, expr, "-", [raw.type]);
|
||||
if (operand.kind === "numLit") return { ...operand, value: -operand.value, loc };
|
||||
return { kind: "unary", op: "-", operand, type: F64, loc };
|
||||
@@ -185,6 +192,8 @@ export function lowerPrefixUnary(lowerer: Lowerer, expr: ts.PrefixUnaryExpressio
|
||||
return { kind: "libCall", fn: "num.fromString", args: [raw], type: F64, loc };
|
||||
}
|
||||
if (raw.type.kind === "bool") return booleanToNumber(raw, loc);
|
||||
// A Date's number hint answers its time value.
|
||||
if (raw.type.kind === "date") return dateTimeValue(raw, loc);
|
||||
const classNumber = classToNumberConversion(lowerer, raw, expr.operand, "unary plus");
|
||||
if (classNumber) return classNumber;
|
||||
const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { keyedHelperName } from "./stable-names.js";
|
||||
import { perKeyOrder } from "./key-order-witness.js";
|
||||
import { lowerDateToPrimitiveCall } from "./builtins/dates.js";
|
||||
import { isForwardedTupleSpread, lowerForwardingApply } from "./typed-arguments.js";
|
||||
import { guardNullProto, nullProtoRecordShape } from "./null-prototype.js";
|
||||
import { lowerDynStreamMemberCall } from "./dyn-streams.js";
|
||||
@@ -1816,6 +1817,8 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
// The element spelling of an unsupported native Web method must fence
|
||||
// before lowering the callee into a checked-dynamic keyed read.
|
||||
if (ts.isElementAccessExpression(expr.expression)) {
|
||||
const datePrimitive = lowerDateToPrimitiveCall(lowerer, expr, expr.expression);
|
||||
if (datePrimitive) return datePrimitive;
|
||||
const timersDispose = lowerMockTimersElementCall(lowerer, expr, expr.expression);
|
||||
if (timersDispose) return timersDispose;
|
||||
const symbolMethod = lowerClassSymbolMethodCall(lowerer, expr);
|
||||
@@ -2456,6 +2459,9 @@ function lowerNumberConstructorValue(
|
||||
};
|
||||
const optionalNumber = lowerOptionalStringNumber(lowerer, arg, loc);
|
||||
if (optionalNumber) return optionalNumber;
|
||||
// A Date's number hint answers its time value.
|
||||
if (arg.type.kind === "date")
|
||||
return { kind: "libCall", fn: "date.valueOf", args: [arg], type: F64, loc };
|
||||
// A program class instance converts through its own methods.
|
||||
const classNumber = classToNumberConversion(lowerer, arg, argNode);
|
||||
if (classNumber) return classNumber;
|
||||
|
||||
@@ -10426,6 +10426,29 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
const time: IrExpr = { kind: "libCall", fn: "date.getTime", args: [arg], type: F64, loc };
|
||||
return { kind: "libCall", fn: "date.newMs", args: [time], type: DATE_T, loc };
|
||||
}
|
||||
// A union (`Date | string | number`) or checked-dynamic value: the
|
||||
// runtime's constructor reads a Date's time value, parses a string
|
||||
// and converts anything else to a primitive and a number.
|
||||
if (
|
||||
(arg.type.kind === "union" || arg.type.kind === "dyn") &&
|
||||
lowerer.dynConvertible(arg.type)
|
||||
) {
|
||||
const value: IrExpr = {
|
||||
kind: "libCall",
|
||||
fn: "date.nativeNewValue",
|
||||
args: [
|
||||
{
|
||||
kind: "dynArrLit",
|
||||
elems: [lowerer.coerceToExpected(arg, DYN)],
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
],
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
return { kind: "libCall", fn: "date.checkedValue", args: [value], type: DATE_T, loc };
|
||||
}
|
||||
lowerer.noLowering(
|
||||
`new Date of '${lowerer.fmt(arg.type)}' values`,
|
||||
args[0]!,
|
||||
|
||||
@@ -2320,6 +2320,7 @@ export function typeofAnswer(arm: IrType): string | null {
|
||||
case "set":
|
||||
case "generator":
|
||||
case "regex":
|
||||
case "date":
|
||||
case "bytes":
|
||||
case "url":
|
||||
case "searchParams":
|
||||
|
||||
@@ -183,13 +183,9 @@ export function lowerGenMethodCall(
|
||||
);
|
||||
}
|
||||
arg = lowerer.lowerExpr(argNode);
|
||||
if (arg.type.kind === "date") {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
argNode,
|
||||
".throw() with a Date value (the exception channel cannot preserve Date's object kind; throw an Error or primitive instead)",
|
||||
);
|
||||
}
|
||||
// A Date throws as its checked-dynamic handle, the same Date object
|
||||
// (the throw statement's rule).
|
||||
if (arg.type.kind === "date") arg = lowerer.coerceToExpected(arg, DYN);
|
||||
if (arg.type.kind === "void" || arg.type.kind === "caught" || isUnitType(arg.type)) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
|
||||
@@ -1624,6 +1624,7 @@ const THROWN_AS_DYN: ReadonlySet<IrType["kind"]> = new Set([
|
||||
"bytes",
|
||||
"func",
|
||||
"regex",
|
||||
"date",
|
||||
]);
|
||||
|
||||
export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt[] | null {
|
||||
@@ -1828,9 +1829,7 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt
|
||||
return { kind: "rethrow", localId: caughtLocal.id, loc: locOf(stmt) };
|
||||
}
|
||||
// Exception-representable values move into the runtime's exception
|
||||
// cell. `throw f()` of a void call has no value; Date has numeric
|
||||
// storage internally, but cannot preserve its object kind through the
|
||||
// untyped catch boundary. Reject both rather than changing JS identity.
|
||||
// cell. `throw f()` of a void call has no value.
|
||||
let value = lowerer.lowerExpr(stmt.expression);
|
||||
if (value.type.kind === "void")
|
||||
lowerer.badType(stmt.expression, lowerer.typeOf(stmt.expression));
|
||||
@@ -1842,19 +1841,12 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt
|
||||
} else if (value.type.kind === "record" && lowerer.dynConvertible(value.type)) {
|
||||
value = { kind: "dynFrom", value, liveRef: true, type: DYN, loc: locOf(stmt.expression) };
|
||||
} else if (THROWN_AS_DYN.has(value.type.kind) && lowerer.dynConvertible(value.type)) {
|
||||
// Arrays, Maps, Sets, typed arrays, Buffers, functions, regexes and
|
||||
// unions over them keep their identity and contents the same way:
|
||||
// the catch binding, `typeof`, `instanceof`, `Array.isArray`,
|
||||
// inspection and typed views read the thrown object.
|
||||
// Arrays, Maps, Sets, typed arrays, Buffers, functions, regexes,
|
||||
// Dates and unions over them keep their identity and contents the
|
||||
// same way: the catch binding, `typeof`, `instanceof`,
|
||||
// `Array.isArray`, inspection and typed views read the thrown object.
|
||||
value = lowerer.coerceInto(stmt.expression, value, DYN);
|
||||
}
|
||||
if (value.type.kind === "date") {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
stmt.expression,
|
||||
"throwing Date values (catch bindings cannot preserve Date's object kind; throw an Error or primitive instead)",
|
||||
);
|
||||
}
|
||||
// A dyn throw rides the REF cell arm (scr_throw_ref over the checked-dynamic tree
|
||||
// node — identity preserved through catch bindings, caughtToDyn
|
||||
// passes it back by reference). The JS-lane traced-call shape:
|
||||
@@ -5450,7 +5442,12 @@ export function lowerVarDecl(
|
||||
if (isTimeValueDateDeclaration(lowerer, decl)) {
|
||||
const loc = locOf(decl);
|
||||
const date = lowerer.lowerExprExpecting(decl.initializer!, DATE_T);
|
||||
const local = lowerer.declareLocal(decl.name, decl.name.text, F64, isLet);
|
||||
const local = lowerer.declareLocal(
|
||||
decl.name,
|
||||
decl.name.text,
|
||||
F64,
|
||||
isLet || lowerer.timeValueDateWrites.has(decl),
|
||||
);
|
||||
lowerer.timeValueDateLocals.add(local);
|
||||
const init: IrExpr = { kind: "libCall", fn: "date.getTime", args: [date], type: F64, loc };
|
||||
return { kind: "varDecl", localId: local.id, init, loc };
|
||||
|
||||
@@ -239,7 +239,7 @@ test("optional union widening keeps refused and missing contracts current", () =
|
||||
unionId: lowerer.unions.recursiveRef(recursive, () => "Recursive"),
|
||||
};
|
||||
expect(lowerer.withUndefinedArmOf(pending)).not.toBeNull();
|
||||
lowerer.unions.finalizeRecursive(recursive, [{ kind: "date" }, STRING]);
|
||||
lowerer.unions.finalizeRecursive(recursive, [{ kind: "promise", inner: F64 }, STRING]);
|
||||
expect(lowerer.withUndefinedArmOf(pending)).toBeNull();
|
||||
expect(lowerer.withUndefinedArmOf(pending)).toBeNull();
|
||||
|
||||
|
||||
@@ -2362,6 +2362,9 @@ export class Lowerer {
|
||||
* Date's time value instead of a Date object (date-values.ts). */
|
||||
readonly timeValueDateDecisions = new Map<ts.Node, boolean>();
|
||||
readonly timeValueDateLocals = new Set<IrLocal>();
|
||||
/** Declarations of those locals that a setter call writes
|
||||
* (date-values.ts). */
|
||||
readonly timeValueDateWrites = new Set<ts.VariableDeclaration>();
|
||||
/** Record bindings whose flow class reaches storage outside the class:
|
||||
* they keep the declared layout, so an initializer from unchecked reads
|
||||
* converts once instead of the binding adopting the widened variant. */
|
||||
|
||||
@@ -2329,6 +2329,13 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [
|
||||
],
|
||||
note: "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice",
|
||||
},
|
||||
{
|
||||
id: "stdlib.date.setters",
|
||||
kind: "stdlib",
|
||||
name: "Date.prototype setters",
|
||||
fns: ["date.setFields", "date.timeClip", "date.store"],
|
||||
note: "setTime and the local and UTC field setters, with their optional arguments",
|
||||
},
|
||||
{
|
||||
id: "stdlib.date.UTC",
|
||||
kind: "stdlib",
|
||||
|
||||
@@ -4858,6 +4858,15 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
* lib-call argument passes the time value itself, and any other use
|
||||
* creates the Date. */
|
||||
"date.fromTime": { argTypes: [F64], result: DATE_T },
|
||||
/** Date.prototype's field setters over a time value
|
||||
* (scr_date_set_fields): time value, setter code, argument count and four
|
||||
* arguments; answers the new time value. */
|
||||
"date.setFields": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 },
|
||||
/** TimeClip of a number (setTime's argument). */
|
||||
"date.timeClip": { argTypes: [F64], result: F64 },
|
||||
/** Stores a time value into a Date object and answers it (a setter's
|
||||
* write). */
|
||||
"date.store": { argTypes: [DATE_T, F64], result: F64 },
|
||||
/** Date.prototype.toString / toDateString / toTimeString and String(date):
|
||||
* V8's local-time formats with the host zone's long name, "Invalid Date"
|
||||
* for an invalid date. Owned (+1) string; never throws. */
|
||||
|
||||
@@ -855,9 +855,8 @@ export function funcOf(params: IrType[], ret: IrType): IrType {
|
||||
* promises nullable-only; declared maybe-promise unions are admitted by
|
||||
* maybePromiseUnionArmsOk. */
|
||||
export function unionContainerArmsOk(arms: IrType[]): boolean {
|
||||
return (
|
||||
arms.every((a, i) => a.kind !== "promise" || arms.every((b, j) => j === i || isUnitType(b))) &&
|
||||
dateUnionArmsOk(arms)
|
||||
return arms.every(
|
||||
(a, i) => a.kind !== "promise" || arms.every((b, j) => j === i || isUnitType(b)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -868,13 +867,7 @@ export function unionContainerArmsOk(arms: IrType[]): boolean {
|
||||
* non-thenable path). Data arms must not be thenable; the type mapper
|
||||
* checks that against the checker types before minting such a union. */
|
||||
export function maybePromiseUnionArmsOk(arms: IrType[]): boolean {
|
||||
return arms.filter((a) => a.kind === "promise").length <= 1 && dateUnionArmsOk(arms);
|
||||
}
|
||||
|
||||
/** A Date arm has a union home beside undefined and null arms only: no
|
||||
* narrowing contract tells it from other data arms yet. */
|
||||
function dateUnionArmsOk(arms: IrType[]): boolean {
|
||||
return arms.every((a, i) => a.kind !== "date" || arms.every((b, j) => j === i || isUnitType(b)));
|
||||
return arms.filter((a) => a.kind === "promise").length <= 1;
|
||||
}
|
||||
|
||||
/** Canonical, injective text form of an IrType — the building block of
|
||||
|
||||
@@ -55,7 +55,6 @@ import {
|
||||
typeEquals,
|
||||
typeKey,
|
||||
maybePromiseUnionArmsOk,
|
||||
unionContainerArmsOk,
|
||||
VOID,
|
||||
} from "./ir.js";
|
||||
import { DYN_CLASS_PROPERTIES, callSiteReturnType, classValueConstructor } from "./ir.js";
|
||||
@@ -814,9 +813,6 @@ export function validateModule(mod: IrModule): IrValidationError[] {
|
||||
loc: noLoc,
|
||||
});
|
||||
}
|
||||
if (arm.kind === "date" && !unionContainerArmsOk(u.arms)) {
|
||||
errors.push({ message: `union ${u.id}: date arm ${i} beside non-unit arms`, loc: noLoc });
|
||||
}
|
||||
if (arm.kind === "record" && !recordsById.has(arm.shapeId)) {
|
||||
errors.push({
|
||||
message: `union ${u.id}: arm ${i} references undeclared shape "${arm.shapeId}"`,
|
||||
@@ -6334,15 +6330,10 @@ function validateFunction(
|
||||
}
|
||||
} else {
|
||||
// throw: any exception-representable payload (the throw
|
||||
// statement's rule). Date cannot retain its object kind in the
|
||||
// untyped exception cell.
|
||||
// statement's rule).
|
||||
if (e.arg === null) {
|
||||
err("genResume throw with no payload", e.loc);
|
||||
} else if (
|
||||
e.arg.type.kind === "void" ||
|
||||
e.arg.type.kind === "caught" ||
|
||||
e.arg.type.kind === "date"
|
||||
) {
|
||||
} else if (e.arg.type.kind === "void" || e.arg.type.kind === "caught") {
|
||||
err(`genResume throw of a ${e.arg.type.kind} value`, e.loc);
|
||||
}
|
||||
}
|
||||
@@ -6783,11 +6774,7 @@ function validateFunction(
|
||||
checkExpr(s.value);
|
||||
// dyn throws are allowed: the dyn node rides the REF cell arm by
|
||||
// reference (the JS-lane `throw err` of a dyn argument).
|
||||
if (
|
||||
s.value.type.kind === "void" ||
|
||||
s.value.type.kind === "caught" ||
|
||||
s.value.type.kind === "date"
|
||||
) {
|
||||
if (s.value.type.kind === "void" || s.value.type.kind === "caught") {
|
||||
err(`throw of a ${s.value.type.kind} value`, s.loc);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -4251,6 +4251,13 @@
|
||||
"status": "static",
|
||||
"note": "one date string, using V8's date parser shared with new Date(dateString)"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.date.setters",
|
||||
"kind": "stdlib",
|
||||
"name": "Date.prototype setters",
|
||||
"status": "static",
|
||||
"note": "setTime and the local and UTC field setters, with their optional arguments"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.date.toDateString",
|
||||
"kind": "stdlib",
|
||||
|
||||
@@ -8,12 +8,10 @@ test("hand-built fib module validates", () => {
|
||||
expect(validateModule(fibModule)).toEqual([]);
|
||||
});
|
||||
|
||||
test("validator rejects Date union arms beside other data arms", () => {
|
||||
const bad = structuredClone(fibModule);
|
||||
bad.unions = [{ id: "u-date", arms: [{ kind: "date" }, { kind: "string" }] }];
|
||||
expect(validateModule(bad).map((e) => e.message)).toContain(
|
||||
"union u-date: date arm 0 beside non-unit arms",
|
||||
);
|
||||
test("validator accepts Date union arms beside other arms", () => {
|
||||
const mixed = structuredClone(fibModule);
|
||||
mixed.unions = [{ id: "u-date", arms: [{ kind: "date" }, { kind: "string" }] }];
|
||||
expect(validateModule(mixed)).toEqual([]);
|
||||
const nullable = structuredClone(fibModule);
|
||||
nullable.unions = [{ id: "u-date", arms: [{ kind: "date" }, { kind: "undefinedT" }] }];
|
||||
expect(validateModule(nullable)).toEqual([]);
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8020-date-setters.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8021-date-coercions.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8022-date-unions.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8023-date-clone-throw.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8024-date-setters-dynamic.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8025-date-subclass-setters.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8026-date-subclass-setters-dynamic.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -11660,6 +11660,56 @@ double scr_date_new_local(double y, double mo, double d,
|
||||
return scr_date_from_local(scr_date_v8_make_day(y, mo, d), scr_date_v8_make_time(h, mi, s, ms));
|
||||
}
|
||||
|
||||
/* Date.prototype's setters over a time value, as V8's builtins-date.cc
|
||||
* computes them: the fields of `t` (local fields for the local setters)
|
||||
* with the first `argc` of them replaced, in the order the setter names
|
||||
* them, by `a`..`d`, through MakeDay/MakeTime/MakeDate, then UTC(t) for
|
||||
* the local setters and TimeClip. An invalid `t` stays invalid, except
|
||||
* that setFullYear and setUTCFullYear start from +0: January 1 of the
|
||||
* epoch year at midnight (local midnight for setFullYear). Returns the new
|
||||
* time value; the caller stores it into the Date. Never throws. */
|
||||
double scr_date_set_fields(double t, double setter_code, double argc_count,
|
||||
double a, double b, double c, double d) {
|
||||
int32_t setter = (int32_t)setter_code, argc = (int32_t)argc_count;
|
||||
bool utc = (setter & SCR_DATE_SET_UTC) != 0;
|
||||
int32_t field = setter & ~SCR_DATE_SET_UTC;
|
||||
ScrDateParts p;
|
||||
if (isnan(t)) {
|
||||
if (field != SCR_DATE_SET_FULL_YEAR) return NAN;
|
||||
p = (ScrDateParts){1970, 0, 1, 4, 0, 0, 0, 0, 0};
|
||||
} else if (utc) {
|
||||
if (!scr_date_utc_parts(t, &p)) return NAN;
|
||||
} else if (!scr_date_local_parts(t, &p)) {
|
||||
return NAN;
|
||||
}
|
||||
double y = (double)p.year, mo = p.month, dt = p.date;
|
||||
double h = p.hours, mi = p.minutes, s = p.seconds, ms = p.milliseconds;
|
||||
double args[4] = {a, b, c, d};
|
||||
double *slots[7] = {&y, &mo, &dt, &h, &mi, &s, &ms};
|
||||
/* The first field each setter names, in slots' order. */
|
||||
int first;
|
||||
switch (field) {
|
||||
case SCR_DATE_SET_MILLISECONDS: first = 6; break;
|
||||
case SCR_DATE_SET_SECONDS: first = 5; break;
|
||||
case SCR_DATE_SET_MINUTES: first = 4; break;
|
||||
case SCR_DATE_SET_HOURS: first = 3; break;
|
||||
case SCR_DATE_SET_DATE: first = 2; break;
|
||||
case SCR_DATE_SET_MONTH: first = 1; break;
|
||||
case SCR_DATE_SET_FULL_YEAR: first = 0; break;
|
||||
default: return NAN;
|
||||
}
|
||||
/* Each setter takes at most the fields from its own down to the next
|
||||
* larger unit's end: setHours up to milliseconds, setMonth up to the
|
||||
* date, setFullYear up to the date. */
|
||||
int last = first <= 2 ? 2 : 6;
|
||||
for (int i = 0; i < argc && first + i <= last && i < 4; i++) *slots[first + i] = args[i];
|
||||
double day = scr_date_v8_make_day(y, mo, dt);
|
||||
double time = scr_date_v8_make_time(h, mi, s, ms);
|
||||
if (!utc) return scr_date_from_local(day, time);
|
||||
if (!isfinite(day) || !isfinite(time)) return NAN;
|
||||
return scr_date_new_ms(day * 86400000.0 + time);
|
||||
}
|
||||
|
||||
/* ── V8's date-string parser, the fallback ─────────────────────────────
|
||||
* scr_date_parse answers the bounded grammars directly and asks
|
||||
* this parser only when they produce NaN. It follows V8's DateParser
|
||||
@@ -13203,6 +13253,27 @@ ScrDate *scr_dyn_native_date_check(const ScrDyn *value, const ScrDynPath *path)
|
||||
static const char scr_date_to_date_string_name[] = "toDateString";
|
||||
static const char scr_date_to_time_string_name[] = "toTimeString";
|
||||
|
||||
/* The field setters a checked-dynamic Date answers (scr_date_set_fields). */
|
||||
static const struct {
|
||||
const char *name;
|
||||
int code, max;
|
||||
} scr_native_date_setters[] = {
|
||||
{"setMilliseconds", SCR_DATE_SET_MILLISECONDS, 1},
|
||||
{"setSeconds", SCR_DATE_SET_SECONDS, 2},
|
||||
{"setMinutes", SCR_DATE_SET_MINUTES, 3},
|
||||
{"setHours", SCR_DATE_SET_HOURS, 4},
|
||||
{"setDate", SCR_DATE_SET_DATE, 1},
|
||||
{"setMonth", SCR_DATE_SET_MONTH, 2},
|
||||
{"setFullYear", SCR_DATE_SET_FULL_YEAR, 3},
|
||||
{"setUTCMilliseconds", SCR_DATE_SET_UTC | SCR_DATE_SET_MILLISECONDS, 1},
|
||||
{"setUTCSeconds", SCR_DATE_SET_UTC | SCR_DATE_SET_SECONDS, 2},
|
||||
{"setUTCMinutes", SCR_DATE_SET_UTC | SCR_DATE_SET_MINUTES, 3},
|
||||
{"setUTCHours", SCR_DATE_SET_UTC | SCR_DATE_SET_HOURS, 4},
|
||||
{"setUTCDate", SCR_DATE_SET_UTC | SCR_DATE_SET_DATE, 1},
|
||||
{"setUTCMonth", SCR_DATE_SET_UTC | SCR_DATE_SET_MONTH, 2},
|
||||
{"setUTCFullYear", SCR_DATE_SET_UTC | SCR_DATE_SET_FULL_YEAR, 3},
|
||||
};
|
||||
|
||||
static ScrDyn *scr_native_date_invoke(void *ptr, ScrDyn *self, const char *method,
|
||||
ScrDyn *const *args, size_t argc, const char *what) {
|
||||
(void)self; (void)what;
|
||||
@@ -13232,6 +13303,25 @@ static ScrDyn *scr_native_date_invoke(void *ptr, ScrDyn *self, const char *metho
|
||||
((ScrDate *)ptr)->ms = scr_date_new_ms(time);
|
||||
return scr_dyn_new_num(((ScrDate *)ptr)->ms);
|
||||
}
|
||||
for (size_t i = 0; i < sizeof scr_native_date_setters / sizeof *scr_native_date_setters; i++) {
|
||||
if (strcmp(method, scr_native_date_setters[i].name)) continue;
|
||||
/* V8's order: ToNumber of the first argument, then the time value,
|
||||
* then (unless the date is invalid) ToNumber of the optional ones the
|
||||
* setter takes. */
|
||||
int code = scr_native_date_setters[i].code, max = scr_native_date_setters[i].max;
|
||||
double values[4] = {NAN, 0, 0, 0};
|
||||
if (argc > 0 && !scr_dyn_number_coerce_js(args[0], &values[0])) return NULL;
|
||||
double t = ((ScrDate *)ptr)->ms;
|
||||
int used = argc < (size_t)max ? (int)argc : max;
|
||||
if (used < 1) used = 1;
|
||||
if (!isnan(t) || (code & ~SCR_DATE_SET_UTC) == SCR_DATE_SET_FULL_YEAR) {
|
||||
for (int k = 1; k < used; k++)
|
||||
if (!scr_dyn_number_coerce_js(args[k], &values[k])) return NULL;
|
||||
}
|
||||
double next = scr_date_set_fields(t, code, used, values[0], values[1], values[2], values[3]);
|
||||
((ScrDate *)ptr)->ms = next;
|
||||
return scr_dyn_new_num(next);
|
||||
}
|
||||
#define DATE_GET(name, fn) if (!strcmp(method, name)) return scr_dyn_new_num(fn(ms))
|
||||
DATE_GET("getFullYear", scr_date_get_full_year_local);
|
||||
DATE_GET("getUTCFullYear", scr_date_get_full_year_utc);
|
||||
@@ -13311,7 +13401,9 @@ static const char *const scr_native_date_methods[] = {
|
||||
"getFullYear", "getUTCFullYear", "getMonth",
|
||||
"getUTCMonth", "getDate", "getUTCDate", "getDay", "getUTCDay", "getHours", "getUTCHours",
|
||||
"getMinutes", "getUTCMinutes", "getSeconds", "getUTCSeconds", "getMilliseconds",
|
||||
"getUTCMilliseconds", "getTimezoneOffset", "setTime",
|
||||
"getUTCMilliseconds", "getTimezoneOffset", "setTime", "setMilliseconds", "setSeconds",
|
||||
"setMinutes", "setHours", "setDate", "setMonth", "setFullYear", "setUTCMilliseconds",
|
||||
"setUTCSeconds", "setUTCMinutes", "setUTCHours", "setUTCDate", "setUTCMonth", "setUTCFullYear",
|
||||
};
|
||||
#define SCR_NATIVE_DATE_METHODS (sizeof scr_native_date_methods / sizeof scr_native_date_methods[0])
|
||||
static SCR_TL ScrDyn *scr_native_date_method_values[SCR_NATIVE_DATE_METHODS];
|
||||
@@ -13397,12 +13489,40 @@ static ScrDyn *scr_native_date_get(void *ptr, const char *key, size_t key_len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* structuredClone of a Date: a new Date with the same time value. */
|
||||
static ScrDyn *scr_native_date_clone(void *h) {
|
||||
return scr_dyn_native_date_from_ms(((ScrDate *)h)->ms);
|
||||
}
|
||||
|
||||
/* A Date crossing threads (postMessage, workerData) carries its time value
|
||||
* in a plain allocation any thread may read and free. */
|
||||
static void *scr_native_date_share(void *h) {
|
||||
double *ms = malloc(sizeof *ms);
|
||||
if (!ms) scr_trap("scriptc: out of memory\n");
|
||||
*ms = ((ScrDate *)h)->ms;
|
||||
return ms;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_native_date_adopt(void *payload) {
|
||||
return scr_dyn_native_date_from_ms(*(double *)payload);
|
||||
}
|
||||
|
||||
static void scr_native_date_drop(void *payload) { free(payload); }
|
||||
|
||||
static const ScrDynHandleTransfer scr_native_date_transfer = {
|
||||
.share = scr_native_date_share, .adopt = scr_native_date_adopt, .drop = scr_native_date_drop};
|
||||
|
||||
/* A checked-dynamic handle for the Date object `date` (borrowed; the
|
||||
* handle retains it), so the value stays the same object. */
|
||||
ScrDyn *scr_dyn_native_date(ScrDate *date) {
|
||||
static const ScrDynHandleOps ops = {
|
||||
"Date", &scr_date_retain_v, &scr_date_release_v, &scr_native_date_invoke,
|
||||
&scr_native_date_get, NULL, NULL, NULL,
|
||||
.cls = "Date",
|
||||
.retain = &scr_date_retain_v,
|
||||
.release = &scr_date_release_v,
|
||||
.invoke = &scr_native_date_invoke,
|
||||
.get = &scr_native_date_get,
|
||||
.clone = &scr_native_date_clone,
|
||||
.transfer = &scr_native_date_transfer,
|
||||
};
|
||||
scr_dyn_handle_install(SCR_DYNH_DATE, &ops);
|
||||
return scr_dyn_new_handle(date, SCR_DYNH_DATE);
|
||||
|
||||
@@ -111,6 +111,23 @@ double scr_date_now(void); /* integer ms since epoch, like Node */
|
||||
extern SCR_TL const double *scr_date_mock_now;
|
||||
double scr_date_new_ms(double ms); /* TimeClip (NaN when invalid) */
|
||||
double scr_date_get_time(double ms);
|
||||
/* Date.prototype's setters (setMilliseconds ... setFullYear and their UTC
|
||||
* forms) over a time value: the new time value for `argc` arguments
|
||||
* `a`..`d` (scr_lib.c). `setter` is an SCR_DATE_SET_* code; it and `argc`
|
||||
* are whole numbers passed as doubles, as the compiler's lib calls pass
|
||||
* numbers. */
|
||||
enum {
|
||||
SCR_DATE_SET_MILLISECONDS = 0,
|
||||
SCR_DATE_SET_SECONDS = 1,
|
||||
SCR_DATE_SET_MINUTES = 2,
|
||||
SCR_DATE_SET_HOURS = 3,
|
||||
SCR_DATE_SET_DATE = 4,
|
||||
SCR_DATE_SET_MONTH = 5,
|
||||
SCR_DATE_SET_FULL_YEAR = 6,
|
||||
SCR_DATE_SET_UTC = 8,
|
||||
};
|
||||
double scr_date_set_fields(double t, double setter, double argc,
|
||||
double a, double b, double c, double d);
|
||||
/* A Date object: the TimeClip'd time value in milliseconds (NaN for an
|
||||
* invalid date). Dates are immutable; their identity is the pointer.
|
||||
* Retain/release follow the runtime's RC conventions (SIZE_MAX immortal). */
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// Date setters: every local and UTC setter with each argument count, the
|
||||
// time values they return, normalization of out-of-range fields, local
|
||||
// setters across a daylight saving transition, setters on invalid dates,
|
||||
// explicit undefined arguments, fractional arguments, and the TimeClip
|
||||
// range. A setter changes the one Date object every alias holds.
|
||||
export {};
|
||||
process.env.TZ = "America/Chicago";
|
||||
|
||||
const show = (label: string, d: Date): void => {
|
||||
console.log(label, d.getTime(), Number.isNaN(d.getTime()) ? "invalid" : d.toISOString());
|
||||
};
|
||||
|
||||
const d = new Date(Date.UTC(2026, 0, 31, 10, 20, 30, 400));
|
||||
console.log(d.setMilliseconds(999), d.getMilliseconds());
|
||||
console.log(d.setSeconds(5), d.setSeconds(6, 7));
|
||||
console.log(d.setMinutes(1), d.setMinutes(2, 3), d.setMinutes(4, 5, 6));
|
||||
console.log(d.setHours(7), d.setHours(8, 9), d.setHours(10, 11, 12), d.setHours(13, 14, 15, 16));
|
||||
console.log(d.setDate(15), d.setMonth(1), d.setMonth(2, 3));
|
||||
console.log(d.setFullYear(2025), d.setFullYear(2024, 1), d.setFullYear(2023, 4, 6));
|
||||
show("local", d);
|
||||
|
||||
const u = new Date(Date.UTC(2026, 0, 31));
|
||||
console.log(u.setUTCMilliseconds(1), u.setUTCSeconds(2, 3), u.setUTCMinutes(4, 5, 6));
|
||||
console.log(u.setUTCHours(7, 8, 9, 10), u.setUTCDate(1), u.setUTCMonth(5, 30));
|
||||
console.log(u.setUTCFullYear(1999), u.setUTCFullYear(2000, 1, 29));
|
||||
show("utc", u);
|
||||
|
||||
// Fields out of range carry into the next larger field, both ways.
|
||||
const n = new Date(Date.UTC(2026, 0, 31, 12));
|
||||
n.setUTCMonth(1);
|
||||
show("Jan 31 + month", n);
|
||||
n.setUTCDate(0);
|
||||
show("day 0", n);
|
||||
n.setUTCMonth(13);
|
||||
show("month 13", n);
|
||||
n.setUTCHours(-1);
|
||||
show("hour -1", n);
|
||||
n.setUTCMinutes(60 * 24 + 1);
|
||||
show("minutes past a day", n);
|
||||
n.setUTCSeconds(-0.5);
|
||||
show("fractional seconds", n);
|
||||
n.setUTCMilliseconds(1.9);
|
||||
show("fractional ms", n);
|
||||
|
||||
// Local setters in a zone with daylight saving time: 2026-03-08 02:30
|
||||
// does not exist in Chicago and 2026-11-01 01:30 happens twice.
|
||||
const spring = new Date(2026, 2, 8, 0, 0);
|
||||
spring.setHours(2, 30);
|
||||
show("spring gap", spring);
|
||||
console.log(spring.getHours(), spring.getMinutes(), spring.getTimezoneOffset());
|
||||
const fall = new Date(2026, 10, 1, 0, 0);
|
||||
fall.setHours(1, 30);
|
||||
show("fall overlap", fall);
|
||||
fall.setMinutes(fall.getMinutes() + 60);
|
||||
show("fall overlap + 60 min", fall);
|
||||
const walk = new Date(2026, 2, 7, 12);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
walk.setDate(walk.getDate() + 1);
|
||||
console.log("walk", walk.getDate(), walk.getHours(), walk.getTimezoneOffset());
|
||||
}
|
||||
|
||||
// Invalid dates: setFullYear starts from +0 local time, the others keep NaN.
|
||||
const bad = new Date(NaN);
|
||||
console.log(bad.setHours(1), bad.setDate(2), bad.setUTCMonth(3), bad.setMilliseconds(4));
|
||||
console.log(bad.setFullYear(2020));
|
||||
show("after setFullYear", bad);
|
||||
const badUtc = new Date("not a date");
|
||||
console.log(badUtc.setUTCFullYear(2021, 6));
|
||||
show("after setUTCFullYear", badUtc);
|
||||
|
||||
// Explicit undefined and NaN arguments make the date invalid; a missing
|
||||
// optional argument keeps the current field.
|
||||
const opt = new Date(Date.UTC(2026, 5, 15, 9));
|
||||
console.log(opt.setUTCHours(10, undefined), Number.isNaN(opt.getTime()));
|
||||
const opt2 = new Date(Date.UTC(2026, 5, 15, 9));
|
||||
console.log(opt2.setUTCMonth(Number.NaN), opt2.setUTCMonth(2));
|
||||
|
||||
// setTime and the TimeClip range.
|
||||
const t = new Date(0);
|
||||
console.log(t.setTime(8.64e15), t.setTime(8.64e15 + 1), t.setTime(-8.64e15), t.setTime(1.5));
|
||||
console.log(t.setTime(-0), Object.is(t.getTime(), 0), t.setTime(Number.NaN));
|
||||
const far = new Date(Date.UTC(275760, 8, 13));
|
||||
console.log(far.getTime(), far.setUTCMilliseconds(1));
|
||||
|
||||
// Every alias sees the change: a second binding, an array slot, a Map
|
||||
// value, a record field, a closure and a parameter.
|
||||
const shared = new Date(Date.UTC(2026, 6, 4));
|
||||
const alias = shared;
|
||||
const list = [shared, new Date(Date.UTC(2000, 0, 1))];
|
||||
const byName = new Map<string, Date>([["shared", shared]]);
|
||||
const holder = { when: shared };
|
||||
const bump = (): number => shared.setUTCDate(shared.getUTCDate() + 1);
|
||||
const nextYear = (date: Date): void => {
|
||||
date.setUTCFullYear(date.getUTCFullYear() + 1);
|
||||
};
|
||||
alias.setUTCHours(6);
|
||||
list[0]!.setUTCMinutes(30);
|
||||
byName.get("shared")!.setUTCSeconds(15);
|
||||
holder.when.setUTCMilliseconds(250);
|
||||
bump();
|
||||
nextYear(shared);
|
||||
show("shared", shared);
|
||||
console.log(alias === shared, list[0] === shared, byName.get("shared") === holder.when);
|
||||
console.log(list.map((x) => x.toISOString()).join(" "));
|
||||
|
||||
// A Date a loop adjusts in place.
|
||||
const cursor = new Date(Date.UTC(2026, 0, 1));
|
||||
let weekdays = 0;
|
||||
while (cursor.getUTCFullYear() === 2026) {
|
||||
const day = cursor.getUTCDay();
|
||||
if (day !== 0 && day !== 6) weekdays++;
|
||||
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
||||
}
|
||||
console.log("weekdays in 2026", weekdays);
|
||||
show("cursor", cursor);
|
||||
@@ -0,0 +1,47 @@
|
||||
// Dates converted to primitives as Node converts them: unary +, Number()
|
||||
// and relational operators read the time value (the number hint), while +
|
||||
// with a string, template literals and String() read the text (a Date's
|
||||
// default hint is a string). Symbol.toPrimitive, valueOf and toString answer
|
||||
// the same values; == compares two Dates by identity and a Date with a
|
||||
// primitive through its default primitive. typeof, truthiness and
|
||||
// instanceof treat a Date as an object, invalid ones included.
|
||||
export {};
|
||||
process.env.TZ = "UTC";
|
||||
|
||||
const a = new Date(Date.UTC(2026, 9, 23, 8, 30));
|
||||
const b = new Date(Date.UTC(2026, 9, 24, 8, 30));
|
||||
const bad = new Date(Number.NaN);
|
||||
|
||||
console.log(+a, -a, +b - +a, (+b - +a) / 3600000, Number(a), Math.max(+a, +b) === b.getTime());
|
||||
console.log(a + "!");
|
||||
console.log(`at ${a}`);
|
||||
console.log(String(b));
|
||||
console.log("" + a === a.toString(), a + "1" === a.toString() + "1");
|
||||
console.log(a[Symbol.toPrimitive]("number"), a[Symbol.toPrimitive]("string") === a.toString());
|
||||
console.log(a[Symbol.toPrimitive]("default") === a.toString(), a.valueOf() === a.getTime());
|
||||
console.log(a < b, a <= b, a > b, a >= b, +b > a.getTime() + 1);
|
||||
|
||||
const same = a;
|
||||
const twin = new Date(a.getTime());
|
||||
console.log(a == same, a == twin, a === twin, a != twin, a.getTime() === twin.getTime());
|
||||
console.log(a == (a.toString() as unknown), a == (a.getTime() as unknown));
|
||||
|
||||
console.log(typeof a, typeof bad, a instanceof Date, bad instanceof Date);
|
||||
console.log(Object.prototype.toString.call(a), !a, !!bad, bad ? "truthy" : "falsy");
|
||||
|
||||
// Invalid dates.
|
||||
console.log(+bad, +bad - +a, `${bad}`, String(bad), bad + "", Number.isNaN(bad.getTime()));
|
||||
console.log(bad.toJSON(), JSON.stringify({ when: bad, at: a }));
|
||||
try {
|
||||
bad.toISOString();
|
||||
} catch (error) {
|
||||
console.log((error as Error).name, (error as Error).message);
|
||||
}
|
||||
|
||||
// Sorting and summing through the number hint.
|
||||
const days = [b, a, new Date(Date.UTC(2026, 0, 1))];
|
||||
days.sort((x, y) => +x - +y);
|
||||
console.log(days.map((x) => x.getUTCDate()).join(","));
|
||||
let span = 0;
|
||||
for (let i = 1; i < days.length; i++) span += +days[i]! - +days[i - 1]!;
|
||||
console.log(span / 86400000);
|
||||
@@ -0,0 +1,68 @@
|
||||
// Dates in unions with other arms: Date | string, Date | number and
|
||||
// Date | a record, narrowed with instanceof and typeof, in arrays, Map
|
||||
// values, optional fields and function results. A Date arm keeps its
|
||||
// identity, so a setter through a union-typed alias changes the original.
|
||||
export {};
|
||||
process.env.TZ = "UTC";
|
||||
|
||||
type When = Date | string | number;
|
||||
|
||||
function describe(when: When): string {
|
||||
if (when instanceof Date) return `date ${when.toISOString()}`;
|
||||
if (typeof when === "string") return `text ${when}`;
|
||||
return `ms ${when}`;
|
||||
}
|
||||
|
||||
function normalize(when: When): Date {
|
||||
return when instanceof Date ? when : new Date(when);
|
||||
}
|
||||
|
||||
const fixed = new Date(Date.UTC(2026, 9, 23));
|
||||
const values: When[] = [fixed, "2026-01-02T03:04:05Z", 0, new Date(Number.NaN)];
|
||||
for (const value of values) {
|
||||
const text = value instanceof Date && Number.isNaN(value.getTime()) ? "invalid" : describe(value);
|
||||
console.log(text);
|
||||
}
|
||||
console.log(normalize(fixed) === fixed, normalize("2000-01-01").getUTCFullYear(), normalize(1e12).getUTCFullYear());
|
||||
|
||||
const held: When = fixed;
|
||||
if (held instanceof Date) held.setUTCHours(12);
|
||||
console.log(fixed.getUTCHours(), held === fixed);
|
||||
|
||||
type Stamp = Date | { label: string; at: number };
|
||||
const stamps: Stamp[] = [fixed, { label: "start", at: 5 }];
|
||||
for (const stamp of stamps) {
|
||||
console.log(stamp instanceof Date ? stamp.getUTCDate() : `${stamp.label}@${stamp.at}`);
|
||||
}
|
||||
|
||||
const byKey = new Map<string, Date | string>();
|
||||
byKey.set("a", fixed);
|
||||
byKey.set("b", "later");
|
||||
for (const [key, value] of byKey) console.log(key, value instanceof Date ? value.getTime() : value);
|
||||
|
||||
interface Task {
|
||||
name: string;
|
||||
due?: Date;
|
||||
done: Date | null;
|
||||
}
|
||||
const tasks: Task[] = [
|
||||
{ name: "write", due: fixed, done: null },
|
||||
{ name: "ship", done: new Date(Date.UTC(2026, 11, 1)) },
|
||||
];
|
||||
for (const task of tasks) {
|
||||
const due = task.due ? task.due.toISOString().slice(0, 10) : "none";
|
||||
const done = task.done === null ? "open" : task.done.toISOString().slice(0, 10);
|
||||
console.log(task.name, due, done);
|
||||
}
|
||||
tasks[0]!.due!.setUTCDate(30);
|
||||
console.log(fixed.getUTCDate(), tasks[0]!.due === fixed);
|
||||
|
||||
function latest(dates: (Date | undefined)[]): Date | string {
|
||||
let best: Date | undefined;
|
||||
for (const date of dates) if (date && (best === undefined || date > best)) best = date;
|
||||
return best ?? "none";
|
||||
}
|
||||
const found = latest([undefined, fixed, new Date(0)]);
|
||||
console.log(found === fixed, latest([]));
|
||||
|
||||
console.log(JSON.stringify({ values, stamps }));
|
||||
@@ -0,0 +1,32 @@
|
||||
// Dates through structuredClone and MessageChannel, which copy them as
|
||||
// new Dates with the same time value, and Dates thrown and rejected as
|
||||
// values, which keep their identity.
|
||||
import { MessageChannel } from "node:worker_threads";
|
||||
process.env.TZ = "UTC";
|
||||
|
||||
const at = new Date(Date.UTC(2026, 9, 23, 7));
|
||||
const copy = structuredClone(at);
|
||||
console.log(copy instanceof Date, copy === at, copy.getTime() === at.getTime());
|
||||
copy.setUTCFullYear(2000);
|
||||
console.log(at.getUTCFullYear(), copy.getUTCFullYear());
|
||||
|
||||
const nested = structuredClone({ at, list: [at, at], invalid: new Date(Number.NaN) });
|
||||
console.log(nested.list[0] instanceof Date, nested.at === at, nested.list.length);
|
||||
console.log(nested.at.toISOString(), Number.isNaN(nested.invalid.getTime()));
|
||||
|
||||
try {
|
||||
throw at;
|
||||
} catch (error) {
|
||||
console.log(error instanceof Date, error === at);
|
||||
}
|
||||
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
port2.on("message", (data: { when: Date; again: Date }) => {
|
||||
const { when, again } = data;
|
||||
console.log("message", when instanceof Date, when === at, again instanceof Date, when.toISOString());
|
||||
port2.close();
|
||||
Promise.reject(at).catch((reason: unknown) => {
|
||||
console.log("rejected", reason instanceof Date, reason === at);
|
||||
});
|
||||
});
|
||||
port1.postMessage({ when: at, again: at });
|
||||
@@ -0,0 +1,24 @@
|
||||
// Date setters in JavaScript, on Dates held as untyped values: each setter
|
||||
// converts its arguments with ToNumber (valueOf runs, undefined is NaN) and
|
||||
// changes the one Date object every alias holds.
|
||||
process.env.TZ = "Europe/Berlin";
|
||||
|
||||
const d = new Date(Date.UTC(2026, 2, 28, 12));
|
||||
const alias = d;
|
||||
const box = { d };
|
||||
console.log(d.setHours(1, 30), d.getHours(), d.getTimezoneOffset());
|
||||
console.log(alias.setDate(29), box.d.getHours(), d.getTimezoneOffset());
|
||||
console.log(d.setMinutes("15"), d.setSeconds(true), d.setMilliseconds(null));
|
||||
console.log(d.setUTCFullYear(2030, 11, 31), d.toISOString());
|
||||
console.log(d.setMonth(undefined), Number.isNaN(d.getTime()));
|
||||
console.log(d.setFullYear(2001), d.toISOString());
|
||||
|
||||
let calls = 0;
|
||||
const later = { valueOf() { calls++; return 45; } };
|
||||
console.log(d.setUTCMinutes(later, later), calls, d.toISOString());
|
||||
|
||||
const other = new Date(0);
|
||||
console.log(other.setHours(5), other.getHours(), typeof d.setMonth);
|
||||
const list = [new Date(0), new Date(86400000)];
|
||||
list.forEach((x, i) => x.setUTCHours(i + 1));
|
||||
console.log(list.map((x) => x.toISOString()).join(" "));
|
||||
@@ -0,0 +1,30 @@
|
||||
// Setters on instances of a class declared `extends Date`: each changes
|
||||
// the instance's time value, which the inherited getters, the subclass's
|
||||
// own methods and toISOString then read; invalid instances follow the same
|
||||
// rules as Dates.
|
||||
export {};
|
||||
process.env.TZ = "Asia/Kolkata";
|
||||
|
||||
class Deadline extends Date {
|
||||
label: string;
|
||||
constructor(label: string, ms: number) {
|
||||
super(ms);
|
||||
this.label = label;
|
||||
}
|
||||
extend(days: number): this {
|
||||
this.setDate(this.getDate() + days);
|
||||
return this;
|
||||
}
|
||||
describe(): string {
|
||||
return `${this.label} ${this.toISOString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
const due = new Deadline("report", Date.UTC(2026, 9, 23, 18, 30));
|
||||
console.log(due.describe());
|
||||
console.log(due.setHours(9, 15), due.getHours(), due.getMinutes());
|
||||
console.log(due.extend(10).describe());
|
||||
console.log(due.setUTCMonth(0, 31), due.setMonth(1), due.getDate(), due.getMonth());
|
||||
console.log(due.setFullYear(2024, 1, 29), due.describe());
|
||||
console.log(due.setTime(Number.NaN), Number.isNaN(due.getTime()), due.setUTCFullYear(2030));
|
||||
console.log(due.describe(), due instanceof Date, due instanceof Deadline);
|
||||
@@ -0,0 +1,17 @@
|
||||
// Setters on instances of a JavaScript class declared `extends Date`, held
|
||||
// as untyped values: the inherited setters change the instance's time value.
|
||||
process.env.TZ = "America/Sao_Paulo";
|
||||
|
||||
class Shift extends Date {
|
||||
start() {
|
||||
this.setHours(8, 0, 0, 0);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
const shift = new Shift(Date.UTC(2026, 4, 5, 15));
|
||||
console.log(shift.start().toISOString(), shift.getHours());
|
||||
console.log(shift.setDate(31), shift.getMonth(), shift.setUTCHours(0), shift.toISOString());
|
||||
const values = [shift, new Shift(0)];
|
||||
for (const value of values) value.setUTCMinutes(45);
|
||||
console.log(values.map((value) => value.toISOString()).join(" "), values[0] === shift);
|
||||
@@ -21,8 +21,6 @@ const fmt = new Intl.NumberFormat();
|
||||
|
||||
// Constructors.
|
||||
const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced
|
||||
const mutableDate = new Date(0);
|
||||
mutableDate.setUTCFullYear(2024); // mutation is outside the read-only Date slice
|
||||
const wr = new WeakRef({});
|
||||
const px = new Proxy({ a: 1 }, {});
|
||||
const buf = new ArrayBuffer(8);
|
||||
@@ -73,14 +71,6 @@ function pick(a: string | number, b: number | boolean): boolean {
|
||||
}
|
||||
const picked = pick(1, 2);
|
||||
|
||||
function throwDate(): void {
|
||||
throw new Date(0); // the exception cell cannot preserve Date's object kind
|
||||
}
|
||||
throwDate();
|
||||
function* dateGenerator(): Generator<number, void, unknown> {
|
||||
yield 1;
|
||||
}
|
||||
dateGenerator().throw(new Date(0));
|
||||
// Related classes may store one object under different union tags. A
|
||||
// structural arm beside them leaves no identity-preserving comparison.
|
||||
class FenceBase {
|
||||
|
||||
@@ -16,7 +16,7 @@ assert-class-fences.ts:12:16 - error SC2020: 'assert.Assert instances used as va
|
||||
|
||||
hint: bind `new Assert(...)` with literal options to a const and only call methods on it (`const a = new Assert({ diff: 'full' }); a.strictEqual(x, y)`)
|
||||
|
||||
assert-class-fences.ts:13:22 - error SC2001: values of type 'object | Assert' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
assert-class-fences.ts:13:22 - error SC2001: values of type 'object | Assert' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
12 | const passed = new Assert();
|
||||
13 | function useInstance(a: Assert | object): void {
|
||||
|
||||
@@ -7,7 +7,7 @@ class-interface-refinements.ts:8:26 - error SC2002: record shapes must match exa
|
||||
|
||||
hint: width subtyping compiles as a copy (each expected field copied off the source; extra fields drop) — this pair is outside the copy's rules; build a literal with exactly the expected fields instead
|
||||
|
||||
class-interface-refinements.ts:12:16 - error SC2001: values of type 'AddedStorage' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
class-interface-refinements.ts:12:16 - error SC2001: values of type 'AddedStorage' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
11 | interface AddedStorage extends Box { extra: string; }
|
||||
12 | function added(view: AddedStorage): string { return view.extra; }
|
||||
@@ -23,7 +23,7 @@ class-interface-refinements.ts:13:13 - error SC2004: uses of 'added' inherit the
|
||||
|
||||
hint: the declaration of 'added' did not compile — fix the diagnostic reported there and these sites clear with it
|
||||
|
||||
class-interface-refinements.ts:16:18 - error SC2001: values of type 'ChangedMethod' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
class-interface-refinements.ts:16:18 - error SC2001: values of type 'ChangedMethod' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
15 | interface ChangedMethod extends Box { read(): number; }
|
||||
16 | function changed(view: ChangedMethod): number { return view.read(); }
|
||||
|
||||
@@ -5,7 +5,7 @@ date-formatters.ts:4:1 - error SC2020: 'Date.prototype.toLocaleTimeString' is pa
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
5 | date.toLocaleString();
|
||||
|
||||
hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale formatters have no lowering
|
||||
hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters and setters, setTime(), and getTimezoneOffset() are supported; locale formatters have no lowering
|
||||
|
||||
date-formatters.ts:5:1 - error SC2020: 'Date.prototype.toLocaleString' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
@@ -14,4 +14,4 @@ date-formatters.ts:5:1 - error SC2020: 'Date.prototype.toLocaleString' is part o
|
||||
| ^~~~~~~~~~~~~~~~~~~~~
|
||||
6 |
|
||||
|
||||
hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale formatters have no lowering
|
||||
hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters and setters, setTime(), and getTimezoneOffset() are supported; locale formatters have no lowering
|
||||
@@ -1,4 +1,4 @@
|
||||
dynamic-representable-types.ts:7:7 - error SC2001: values of type '[number, (string | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
dynamic-representable-types.ts:7:7 - error SC2001: values of type '[number, (string | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
6 | const bare: any = 41;
|
||||
7 | const optTuple: [number, string?] = [1];
|
||||
|
||||
@@ -12,7 +12,7 @@ generics.ts:30:10 - error SC1090: '.join()' on arrays of this element type (numb
|
||||
| ^~~~~~~~~~~
|
||||
31 | }
|
||||
|
||||
generics.ts:44:7 - error SC2001: values of type 'typeof Box' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
generics.ts:44:7 - error SC2001: values of type 'typeof Box' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
43 | // The uninstantiated family as a value: no thunk, no single ctor ABI.
|
||||
44 | const BoxAlias = Box;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
maps-surface.ts:21:11 - error SC2001: values of type '(v: number, k: string, theMap: Map<string, number>) => void' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
maps-surface.ts:21:11 - error SC2001: values of type '(v: number, k: string, theMap: Map<string, number>) => void' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
20 | // The forEach callback receives (value, key) — no third map parameter.
|
||||
21 | m.forEach((v, k, theMap) => console.log(k, v));
|
||||
|
||||
@@ -5,7 +5,7 @@ regex-match-destructuring-fences.ts:6:16 - error SC1031: rest bindings over rege
|
||||
| ^~~~~~~
|
||||
7 | const { input = "none" } = m;
|
||||
|
||||
regex-match-destructuring-fences.ts:6:19 - error SC2001: values of type '{ [n: number]: string; input: string; 0: string; length: number; toString(): string; toLocaleString(): string; toLocaleString(locales: string | string[], options?: (NumberFormatOptions & DateTimeFormatOptions) | undefined): string; ... 39 more ...; with(index: number, value: string): string[]; }' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
regex-match-destructuring-fences.ts:6:19 - error SC2001: values of type '{ [n: number]: string; input: string; 0: string; length: number; toString(): string; toLocaleString(): string; toLocaleString(locales: string | string[], options?: (NumberFormatOptions & DateTimeFormatOptions) | undefined): string; ... 39 more ...; with(index: number, value: string): string[]; }' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
5 | const m = /(a)(b)?/.exec("ab")!;
|
||||
6 | const { index, ...rest } = m;
|
||||
|
||||
@@ -43,7 +43,7 @@ retired-engine-2103-optional-tuple-elements.ts:19:1 - error SC2004: uses of 'f2'
|
||||
|
||||
hint: the declaration of 'f2' did not compile — fix the diagnostic reported there and these sites clear with it
|
||||
|
||||
retired-engine-2103-optional-tuple-elements.ts:20:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
retired-engine-2103-optional-tuple-elements.ts:20:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
19 | f2([]);
|
||||
20 | const t: [string?, number?] = ["x"];
|
||||
@@ -59,7 +59,7 @@ retired-engine-2103-optional-tuple-elements.ts:21:13 - error SC2004: uses of 't'
|
||||
|
||||
hint: the declaration of 't' did not compile — fix the diagnostic reported there and these sites clear with it
|
||||
|
||||
retired-engine-2103-optional-tuple-elements.ts:22:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
retired-engine-2103-optional-tuple-elements.ts:22:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
21 | console.log(t.length, `${t[0]} ${t[1]}`);
|
||||
22 | const full: [string?, number?] = ["y", 7];
|
||||
@@ -75,14 +75,14 @@ retired-engine-2103-optional-tuple-elements.ts:23:13 - error SC2004: uses of 'fu
|
||||
|
||||
hint: the declaration of 'full' did not compile — fix the diagnostic reported there and these sites clear with it
|
||||
|
||||
retired-engine-2103-optional-tuple-elements.ts:24:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
retired-engine-2103-optional-tuple-elements.ts:24:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
23 | console.log(full.length, `${full[0]} ${full[1]}`);
|
||||
24 | const none: [string?, number?] = [];
|
||||
| ^~~~
|
||||
25 | console.log(none.length);
|
||||
|
||||
retired-engine-2103-optional-tuple-elements.ts:24:34 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
retired-engine-2103-optional-tuple-elements.ts:24:34 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
23 | console.log(full.length, `${full[0]} ${full[1]}`);
|
||||
24 | const none: [string?, number?] = [];
|
||||
@@ -98,7 +98,7 @@ retired-engine-2103-optional-tuple-elements.ts:25:13 - error SC2004: uses of 'no
|
||||
|
||||
hint: the declaration of 'none' did not compile — fix the diagnostic reported there and these sites clear with it
|
||||
|
||||
retired-engine-2103-optional-tuple-elements.ts:27:7 - error SC2001: values of type '[string, ...number[]]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
retired-engine-2103-optional-tuple-elements.ts:27:7 - error SC2001: values of type '[string, ...number[]]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
26 | // A rest-element tuple rides the same island representation.
|
||||
27 | const r: [string, ...number[]] = ["head", 1, 2, 3];
|
||||
|
||||
@@ -55,88 +55,65 @@ stdlib-fence.ts:23:11 - error SC2020: 'new Date with spread arguments' is part o
|
||||
22 | // Constructors.
|
||||
23 | const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
24 | const mutableDate = new Date(0);
|
||||
24 | const wr = new WeakRef({});
|
||||
|
||||
hint: new Date(), new Date(milliseconds), new Date(dateString), and new Date(year, monthIndex, ...) with listed arguments are supported
|
||||
|
||||
stdlib-fence.ts:25:1 - error SC2020: 'Date.prototype.setUTCFullYear with arguments' is part of the standard library types but has no scriptc lowering yet
|
||||
stdlib-fence.ts:24:7 - error SC2020: 'WeakRef<{}>' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
24 | const mutableDate = new Date(0);
|
||||
25 | mutableDate.setUTCFullYear(2024); // mutation is outside the read-only Date slice
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
26 | const wr = new WeakRef({});
|
||||
|
||||
hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale formatters have no lowering
|
||||
|
||||
stdlib-fence.ts:26:7 - error SC2020: 'WeakRef<{}>' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
25 | mutableDate.setUTCFullYear(2024); // mutation is outside the read-only Date slice
|
||||
26 | const wr = new WeakRef({});
|
||||
23 | const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced
|
||||
24 | const wr = new WeakRef({});
|
||||
| ^~
|
||||
27 | const px = new Proxy({ a: 1 }, {});
|
||||
25 | const px = new Proxy({ a: 1 }, {});
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
stdlib-fence.ts:26:12 - error SC2020: 'new WeakRef' is part of the standard library types but has no scriptc lowering yet
|
||||
stdlib-fence.ts:24:12 - error SC2020: 'new WeakRef' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
25 | mutableDate.setUTCFullYear(2024); // mutation is outside the read-only Date slice
|
||||
26 | const wr = new WeakRef({});
|
||||
23 | const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced
|
||||
24 | const wr = new WeakRef({});
|
||||
| ^~~~~~~~~~~~~~~
|
||||
27 | const px = new Proxy({ a: 1 }, {});
|
||||
25 | const px = new Proxy({ a: 1 }, {});
|
||||
|
||||
hint: deref()-after-collect exposes GC timing — genuinely dynamic; hold a strong reference instead
|
||||
|
||||
stdlib-fence.ts:41:31 - error SC1090: '.split()' on a 'RegExp | string' separator (pass a string, or a regex literal) is not supported yet
|
||||
stdlib-fence.ts:39:31 - error SC1090: '.split()' on a 'RegExp | string' separator (pass a string, or a regex literal) is not supported yet
|
||||
|
||||
40 | const norm = "abc".normalize();
|
||||
41 | const limited = "a,b,c".split(Math.random() ? "," : /,/, 2); // union separators stay fenced; limits lower
|
||||
38 | const norm = "abc".normalize();
|
||||
39 | const limited = "a,b,c".split(Math.random() ? "," : /,/, 2); // union separators stay fenced; limits lower
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
42 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer)
|
||||
40 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer)
|
||||
|
||||
stdlib-fence.ts:43:19 - error SC2020: 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet
|
||||
stdlib-fence.ts:41:19 - error SC2020: 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
42 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer)
|
||||
43 | const localized = (1234.5).toLocaleString();
|
||||
40 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer)
|
||||
41 | const localized = (1234.5).toLocaleString();
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
44 | // Unlowered call FORMS of lowered members. (push is fully variadic now —
|
||||
42 | // Unlowered call FORMS of lowered members. (push is fully variadic now —
|
||||
|
||||
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:1 - error SC2020: '.forEach with 2 arguments' is part of the standard library types but has no scriptc lowering yet
|
||||
stdlib-fence.ts:47:1 - error SC2020: '.forEach with 2 arguments' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
48 | const joined = nums.join(); // the omitted separator lowers
|
||||
49 | nums.forEach((x) => console.log(x), { unused: true }); // thisArg stays fenced
|
||||
46 | const joined = nums.join(); // the omitted separator lowers
|
||||
47 | nums.forEach((x) => console.log(x), { unused: true }); // thisArg stays fenced
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
50 | const pos = "abc".includes("b", 1); // the position form lowers now (indexOf's clamp)
|
||||
48 | const pos = "abc".includes("b", 1); // the position form lowers now (indexOf's clamp)
|
||||
|
||||
hint: the thisArg parameter has no lowering — use an arrow function
|
||||
|
||||
stdlib-fence.ts:77:9 - error SC1090: throwing Date values (catch bindings cannot preserve Date's object kind; throw an Error or primitive instead) are not supported yet
|
||||
stdlib-fence.ts:84:10 - error SC1090: comparisons of related class values beside structural union members (narrow first: check a discriminant field, or compare with '!== undefined'/'!== null' for unit arms) are not supported yet
|
||||
|
||||
76 | function throwDate(): void {
|
||||
77 | throw new Date(0); // the exception cell cannot preserve Date's object kind
|
||||
| ^~~~~~~~~~~
|
||||
78 | }
|
||||
|
||||
stdlib-fence.ts:83:23 - error SC1090: .throw() with a Date value (the exception channel cannot preserve Date's object kind; throw an Error or primitive instead) is not supported yet
|
||||
|
||||
82 | }
|
||||
83 | dateGenerator().throw(new Date(0));
|
||||
| ^~~~~~~~~~~
|
||||
84 | // Related classes may store one object under different union tags. A
|
||||
|
||||
stdlib-fence.ts:94:10 - error SC1090: comparisons of related class values beside structural union members (narrow first: check a discriminant field, or compare with '!== undefined'/'!== null' for unit arms) are not supported yet
|
||||
|
||||
93 | function relatedStrict(a: FenceValue, b: FenceValue): boolean {
|
||||
94 | return a === b;
|
||||
83 | function relatedStrict(a: FenceValue, b: FenceValue): boolean {
|
||||
84 | return a === b;
|
||||
| ^~~~~~~
|
||||
95 | }
|
||||
85 | }
|
||||
|
||||
stdlib-fence.ts:97:10 - error SC2020: 'Object.is over related class values beside structural union members' is part of the standard library types but has no scriptc lowering yet
|
||||
stdlib-fence.ts:87:10 - error SC2020: 'Object.is over related class values beside structural union members' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
96 | function relatedSameValue(a: FenceValue, b: FenceSub): boolean {
|
||||
97 | return Object.is(a, b);
|
||||
86 | function relatedSameValue(a: FenceValue, b: FenceSub): boolean {
|
||||
87 | return Object.is(a, b);
|
||||
| ^~~~~~~~~~~~~~~
|
||||
98 | }
|
||||
88 | }
|
||||
|
||||
hint: narrow first: check a discriminant field, or compare with '!== undefined'/'!== null' for unit arms
|
||||
@@ -12,14 +12,14 @@ tuples.ts:12:6 - error SC1090: tuple indexing with a non-literal index (tuples a
|
||||
| ^
|
||||
13 |
|
||||
|
||||
tuples.ts:15:7 - error SC2001: values of type '[string, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
tuples.ts:15:7 - error SC2001: values of type '[string, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
14 | // Optional and rest elements have no fixed arity/shape.
|
||||
15 | const opt: [string, number?] = ["x"];
|
||||
| ^~~
|
||||
16 | const rest: [string, ...number[]] = ["y", 1, 2];
|
||||
|
||||
tuples.ts:16:7 - error SC2001: values of type '[string, ...number[]]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
tuples.ts:16:7 - error SC2001: values of type '[string, ...number[]]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
15 | const opt: [string, number?] = ["x"];
|
||||
16 | const rest: [string, ...number[]] = ["y", 1, 2];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
union-clone-override-width.ts:8:18 - error SC2001: values of type '{ kind: "left"; left: number; payload: WiderPayload; } | { kind: "right"; right: number; payload: WiderPayload; }' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')
|
||||
union-clone-override-width.ts:8:18 - error SC2001: values of type '{ kind: "left"; left: number; payload: WiderPayload; } | { kind: "right"; right: number; payload: WiderPayload; }' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
7 | // copy its record. The inferred spread result keeps the wider payload.
|
||||
8 | const result = { ...source, payload };
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2891, "san": 4454 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2589, "san": 3385 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2786, "san": 3724 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2847, "san": 33492 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2645, "san": 3529 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2383, "san": 3249 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2483, "san": 3294 }
|
||||
Reference in New Issue
Block a user