mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-07 08:33:50 +08:00
Merge opt/variadic-generic-wrappers into opt/override-variance-error-expando
Brings in #1433's review follow-up (now merged into #1413). In `key in err`, own `stack` and Object.prototype keys answer true, keys built-in errors keep outside their class fields stay fenced, and every other key keeps this branch's answer: a declaring subclass or a runtime-set own property. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { lowerReadlineCursorCall } from "./builtins/readline.js";
|
||||
import { lowerMonitorEventLoopDelayCall } from "./builtins/performance.js";
|
||||
import {
|
||||
lowerEnvironmentData,
|
||||
lowerReceiveMessageOnPort,
|
||||
@@ -69,6 +70,9 @@ export function lowerBuiltinCall(
|
||||
return lowerAddAbortListener(lowerer, expr, loc);
|
||||
}
|
||||
if (bi.module === "events" && bi.member === "once") return lowerEventsOnce(lowerer, expr, loc);
|
||||
if (bi.module === "perf_hooks" && bi.member === "monitorEventLoopDelay") {
|
||||
return lowerMonitorEventLoopDelayCall(lowerer, expr);
|
||||
}
|
||||
if (bi.module === "events") {
|
||||
const helper = lowerEventsEmitterHelper(lowerer, expr, bi.member, loc);
|
||||
if (helper) return helper;
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from "../../../ir/ir.js";
|
||||
import { PERF_ENTRY_FIELDS, PERF_ENTRY_MARKER, perfEntryJsonShape } from "../../type-mapper.js";
|
||||
import { builtinImportOf } from "./module-bindings.js";
|
||||
import { lowerOptionalArgument } from "../optional-arguments.js";
|
||||
import { checkedIterableSpread } from "../checked-iterable-spread.js";
|
||||
|
||||
/** True for any spelling of node:perf_hooks' `performance` object — the
|
||||
@@ -663,21 +664,11 @@ export function lowerHistogramMethodCall(
|
||||
);
|
||||
}
|
||||
|
||||
/** `monitorEventLoopDelay(options?)` from node:perf_hooks (a named import
|
||||
* or a namespace member): a new sampling histogram handle. The resolution
|
||||
* must be a literal-free number expression or an object literal option. */
|
||||
export function lowerMonitorEventLoopDelayCall(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.CallExpression,
|
||||
): IrExpr | null {
|
||||
const callee = expr.expression;
|
||||
const bi = ts.isIdentifier(callee)
|
||||
? builtinImportOf(lowerer, callee)
|
||||
: ts.isPropertyAccessExpression(callee) && !callee.questionDotToken
|
||||
? lowerer.builtinMemberOf(callee)
|
||||
: null;
|
||||
if (bi === null || bi.module !== "perf_hooks" || bi.member !== "monitorEventLoopDelay")
|
||||
return null;
|
||||
/** `monitorEventLoopDelay(options?)` from node:perf_hooks, in any import
|
||||
* spelling (`lowerBuiltinCall`): a new sampling histogram handle. The
|
||||
* options are an inline object literal; the runtime validates the
|
||||
* resolution as Node does (an integer of at least 1). */
|
||||
export function lowerMonitorEventLoopDelayCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
const loc = locOf(expr);
|
||||
const args = expr.arguments.nodes;
|
||||
let resolution: IrExpr = numLit(10, loc);
|
||||
@@ -698,8 +689,9 @@ export function lowerMonitorEventLoopDelayCall(
|
||||
);
|
||||
}
|
||||
for (const property of options.properties.nodes) {
|
||||
const shorthand = ts.isShorthandPropertyAssignment(property);
|
||||
if (
|
||||
!ts.isPropertyAssignment(property) ||
|
||||
(!shorthand && !ts.isPropertyAssignment(property)) ||
|
||||
!ts.isIdentifier(property.name) ||
|
||||
property.name.text !== "resolution"
|
||||
) {
|
||||
@@ -709,7 +701,13 @@ export function lowerMonitorEventLoopDelayCall(
|
||||
"resolution is the one option",
|
||||
);
|
||||
}
|
||||
resolution = lowerer.lowerExprExpecting(property.initializer, F64);
|
||||
// An undefined resolution takes the default, as Node's destructuring.
|
||||
resolution = lowerOptionalArgument(
|
||||
lowerer,
|
||||
shorthand ? property.name : property.initializer,
|
||||
F64,
|
||||
numLit(10, loc),
|
||||
);
|
||||
}
|
||||
}
|
||||
return { kind: "libCall", fn: "perf.eldNew", args: [resolution], type: F64, loc };
|
||||
|
||||
@@ -1144,6 +1144,24 @@ function lowerPrivateIn(
|
||||
}
|
||||
|
||||
/** The string keys every record inherits from Object.prototype. */
|
||||
/** Keys built-in errors carry outside their class fields: Node's system
|
||||
* error properties, and AggregateError's, SuppressedError's and
|
||||
* AssertionError's own members. */
|
||||
const BUILTIN_ERROR_SLOT_KEYS: ReadonlySet<string> = new Set([
|
||||
"errno",
|
||||
"syscall",
|
||||
"path",
|
||||
"dest",
|
||||
"info",
|
||||
"errors",
|
||||
"error",
|
||||
"suppressed",
|
||||
"actual",
|
||||
"expected",
|
||||
"operator",
|
||||
"generatedMessage",
|
||||
]);
|
||||
|
||||
const OBJECT_PROTOTYPE_KEYS: ReadonlySet<string> = new Set([
|
||||
"constructor",
|
||||
"__defineGetter__",
|
||||
@@ -1418,6 +1436,9 @@ export function lowerInExpression(
|
||||
loc,
|
||||
};
|
||||
if (key === "message" || key === "name") return always();
|
||||
// Every Error has its own `stack`, and Error.prototype's `toString`
|
||||
// and `constructor` are among Object.prototype's keys.
|
||||
if (key === "stack" || OBJECT_PROTOTYPE_KEYS.has(key)) return always();
|
||||
// A member the static class declares is always present.
|
||||
const declares = (c: ClassInfo): boolean =>
|
||||
c.fields.has(key) ||
|
||||
@@ -1426,12 +1447,12 @@ export function lowerInExpression(
|
||||
lowerer.findMethodOn(c, `set:${key}`) !== null;
|
||||
const staticInfo = lowerer.classes.get(recv.type.className);
|
||||
if (staticInfo && declares(staticInfo)) return always();
|
||||
// Any other key: a descendant class that declares it (instanceof),
|
||||
// Any other key: a descendant class that declares it (instanceof), or
|
||||
// an own property set at runtime (a throw site's extra fields,
|
||||
// Object.assign, writes through a dynamic view), or an
|
||||
// Object.prototype member. `stack` stays fenced: Node has one, a
|
||||
// compiled error has none.
|
||||
if (key !== "stack") {
|
||||
// Object.assign, writes through a dynamic view). Keys built-in errors
|
||||
// keep outside their class fields depend on the runtime error and
|
||||
// stay fenced.
|
||||
if (!BUILTIN_ERROR_SLOT_KEYS.has(key)) {
|
||||
const stable = recv.kind === "varRef" || recv.kind === "caughtNarrow";
|
||||
const slot = stable ? null : lowerer.declareHiddenLocal("%inRecv", recv.type);
|
||||
const ref = (): IrExpr => (slot ? varRef(slot.id, recv.type, loc) : recv);
|
||||
|
||||
@@ -151,7 +151,6 @@ import {
|
||||
fencePerfEntryKeyWalk,
|
||||
isPerfEntryType,
|
||||
lowerHistogramMethodCall,
|
||||
lowerMonitorEventLoopDelayCall,
|
||||
lowerPerfHooksCall,
|
||||
perfEntryTag,
|
||||
} from "./builtins/performance.js";
|
||||
@@ -254,16 +253,6 @@ const ZLIB_STREAM_METHODS: ReadonlySet<string> = new Set(["flush", "params", "cl
|
||||
|
||||
export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
const loc = locOf(expr);
|
||||
// node:perf_hooks monitorEventLoopDelay (named import or namespace member).
|
||||
if (
|
||||
(ts.isIdentifier(expr.expression) && expr.expression.text === "monitorEventLoopDelay") ||
|
||||
(ts.isPropertyAccessExpression(expr.expression) &&
|
||||
expr.expression.name.text === "monitorEventLoopDelay")
|
||||
) {
|
||||
const histogram = lowerMonitorEventLoopDelayCall(lowerer, expr);
|
||||
if (histogram) return histogram;
|
||||
}
|
||||
|
||||
// Object's checker return type is `any`. Claim known object inputs
|
||||
// before generic checked-dynamic dispatch tries to load the constructor.
|
||||
if (
|
||||
|
||||
@@ -366,6 +366,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"perf.entries",
|
||||
"perf.clearMarks",
|
||||
"perf.clearMeasures",
|
||||
// monitorEventLoopDelay's resolution: Node's ERR_OUT_OF_RANGE.
|
||||
"perf.eldNew",
|
||||
// Non-TTY stdin: Node's stdin has no setRawMode member, so the call is
|
||||
// the exact catchable TypeError.
|
||||
"process.stdinSetRawMode",
|
||||
|
||||
@@ -365,7 +365,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
/** perf_hooks event-loop metrics (scr_perf_loop.c): loopStart is the
|
||||
* loop's start in performance.now() ms (0 before it), idleTime the idle
|
||||
* ms since. eldNew(resolution ms) creates a monitorEventLoopDelay
|
||||
* histogram handle; enable/disable answer whether the state changed;
|
||||
* histogram handle (MAY THROW Node's resolution RangeError); enable/disable answer whether the state changed;
|
||||
* eldPercentile expects Node's validated range; eldStat(which) reads
|
||||
* 0 min, 1 max, 2 mean, 3 stddev, 4 count, 5 exceeds. */
|
||||
"perf.loopStart": { argTypes: [], result: F64 },
|
||||
|
||||
@@ -54,6 +54,11 @@ const LIB_MODE_REFUSED_PREFIXES: readonly [string, string][] = [
|
||||
["webcrypto.", "WebCrypto (crypto.subtle)"],
|
||||
["fs.watch", "fs.watch"],
|
||||
["watcher.", "fs.watch"],
|
||||
// The event-loop metrics sample the loop and its timers (scr_perf_loop.c),
|
||||
// which library links exclude.
|
||||
["perf.loopStart", "performance.eventLoopUtilization"],
|
||||
["perf.idleTime", "performance.eventLoopUtilization"],
|
||||
["perf.eld", "perf_hooks.monitorEventLoopDelay"],
|
||||
["test.", "the node:test surface"],
|
||||
["readable.", "the node:stream surface"],
|
||||
["writable.", "the node:stream surface"],
|
||||
@@ -158,6 +163,7 @@ export function moduleLibAsyncSurface(mod: IrModule): { surface: string; loc: Sr
|
||||
[features.nodeTest, "the node:test surface"],
|
||||
[features.dynAsync, "the checked-dynamic async surface"],
|
||||
[features.dc, "the diagnostics_channel surface"],
|
||||
[features.perfLoop, "the perf_hooks event-loop metrics"],
|
||||
];
|
||||
for (const [on, surface] of coarse) {
|
||||
if (on) return { surface, loc: entryLoc };
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
@@ -212,7 +214,27 @@ static void scr_eld_cleanup(void) {
|
||||
scr_elds_len = 0;
|
||||
}
|
||||
|
||||
/* Node's validateInteger(resolution, 'options.resolution', 1): a catchable
|
||||
* RangeError (ERR_OUT_OF_RANGE) for a non-integer or a value out of range,
|
||||
* with the value as util.inspect renders it ("-0" included). */
|
||||
static bool scr_eld_resolution_ok(double resolution) {
|
||||
bool integer = isfinite(resolution) && trunc(resolution) == resolution;
|
||||
if (integer && resolution >= 1 && resolution <= 9007199254740991.0) return true;
|
||||
char received[48];
|
||||
if (resolution == 0 && signbit(resolution))
|
||||
memcpy(received, "-0", 3);
|
||||
else
|
||||
scr_num_received(resolution, received);
|
||||
char msg[192];
|
||||
int len = snprintf(msg, sizeof msg,
|
||||
"The value of \"options.resolution\" is out of range. It must be %s. Received %s",
|
||||
integer ? ">= 1 && <= 9007199254740991" : "an integer", received);
|
||||
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
||||
return false;
|
||||
}
|
||||
|
||||
double scr_eld_new(double resolution) {
|
||||
if (!scr_eld_resolution_ok(resolution)) return 0;
|
||||
if (scr_elds_len == 0 && scr_elds == NULL) scr_atexit(scr_eld_cleanup);
|
||||
ScrEld *h = calloc(1, sizeof(ScrEld));
|
||||
ScrEld **grown = realloc(scr_elds, (scr_elds_len + 1) * sizeof(ScrEld *));
|
||||
@@ -223,7 +245,7 @@ double scr_eld_new(double resolution) {
|
||||
}
|
||||
scr_elds = grown;
|
||||
h->min_value = INT64_MAX;
|
||||
h->resolution = resolution >= 1 ? resolution : 10;
|
||||
h->resolution = resolution;
|
||||
h->tick = scr_closure_new((void *)scr_eld_tick, 0);
|
||||
scr_elds[scr_elds_len++] = h;
|
||||
return (double)scr_elds_len;
|
||||
|
||||
@@ -23,3 +23,9 @@ function retryable(err: Error): boolean {
|
||||
}
|
||||
console.log(retryable(new Error("x")), retryable(new HttpError("a", 500)), retryable(new Timeout("t", 504)));
|
||||
console.log(describe(new Timeout("t", 504)));
|
||||
// Keys every Error has: its own stack, and Error.prototype's and
|
||||
// Object.prototype's members.
|
||||
function inherent(err: Error): boolean[] {
|
||||
return ["stack" in err, "toString" in err, "constructor" in err, "hasOwnProperty" in err];
|
||||
}
|
||||
console.log(inherent(new Error("s")), inherent(new HttpError("h", 400)), inherent(new TypeError("t")));
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Event-loop monitoring under @types/node: monitorEventLoopDelay's histogram,
|
||||
// eventLoopUtilization deltas, and an unref'd interval typed NodeJS.Timeout.
|
||||
import { monitorEventLoopDelay, performance } from "node:perf_hooks";
|
||||
import * as perfHooks from "node:perf_hooks";
|
||||
import { monitorEventLoopDelay as eld } from "node:perf_hooks";
|
||||
|
||||
const histogram = monitorEventLoopDelay({ resolution: 10 });
|
||||
console.log("enable", histogram.enable(), histogram.enable());
|
||||
@@ -47,3 +49,22 @@ const interval = setInterval(() => {
|
||||
}, 50);
|
||||
interval.unref();
|
||||
console.log("hasRef", interval.hasRef());
|
||||
|
||||
// Other import spellings, and Node's resolution validation.
|
||||
const aliased = eld();
|
||||
const namespaced = perfHooks.monitorEventLoopDelay({ resolution: 20 });
|
||||
console.log("spellings", aliased.enable(), namespaced.enable(), aliased.disable(), namespaced.disable());
|
||||
for (const resolution of [0, -0, -1, 1.5, NaN, Infinity, 1e16, 1]) {
|
||||
try {
|
||||
eld({ resolution });
|
||||
console.log("created", resolution);
|
||||
} catch (e) {
|
||||
console.log((e as Error).name, (e as { code?: string }).code, (e as Error).message);
|
||||
}
|
||||
}
|
||||
// An undefined resolution takes Node's default.
|
||||
function withResolution(resolution?: number): boolean {
|
||||
const h = monitorEventLoopDelay({ resolution });
|
||||
return h.enable() && h.disable();
|
||||
}
|
||||
console.log("defaults", withResolution(), withResolution(20), eld({ resolution: undefined }).enable());
|
||||
|
||||
@@ -73,6 +73,21 @@ describe("async_free detection over the IR", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("the event-loop metrics refuse", async () => {
|
||||
expect(
|
||||
await surfaceOf(
|
||||
"eld",
|
||||
`import { monitorEventLoopDelay } from "node:perf_hooks";\nconst h = monitorEventLoopDelay();\nconsole.log(h.enable());\n`,
|
||||
),
|
||||
).toContain("monitorEventLoopDelay");
|
||||
expect(
|
||||
await surfaceOf(
|
||||
"elu",
|
||||
`import { performance } from "node:perf_hooks";\nconsole.log(performance.eventLoopUtilization().idle >= 0);\n`,
|
||||
),
|
||||
).toContain("eventLoopUtilization");
|
||||
});
|
||||
|
||||
test("child_process refuses even in its synchronous spelling", async () => {
|
||||
expect(
|
||||
await surfaceOf(
|
||||
|
||||
@@ -569,10 +569,13 @@ describe("library profile fences", () => {
|
||||
expect(processIds).toContain("node-builtin.process.isTTY");
|
||||
expect(r.profile.fences[1]!.surfaces.every((s) => s.detector !== undefined)).toBe(true);
|
||||
// The user-timing timeline reads the same clock (mark() and measure()
|
||||
// default to performance.now()), so it joins the perf_hooks family.
|
||||
// default to performance.now()), and so do the event-loop metrics, so
|
||||
// they join the perf_hooks family.
|
||||
expect(r.profile.fences[2]!.surfaces.map((s) => s.id)).toEqual([
|
||||
"node-builtin.perf_hooks.monitorEventLoopDelay",
|
||||
"node-builtin.perf_hooks.performance.clearMarks",
|
||||
"node-builtin.perf_hooks.performance.clearMeasures",
|
||||
"node-builtin.perf_hooks.performance.eventLoopUtilization",
|
||||
"node-builtin.perf_hooks.performance.getEntries",
|
||||
"node-builtin.perf_hooks.performance.mark",
|
||||
"node-builtin.perf_hooks.performance.measure",
|
||||
|
||||
Reference in New Issue
Block a user