diff --git a/packages/compiler/src/frontend/lowering/builtin-calls.ts b/packages/compiler/src/frontend/lowering/builtin-calls.ts index 7e1852600..7d3a5d41a 100644 --- a/packages/compiler/src/frontend/lowering/builtin-calls.ts +++ b/packages/compiler/src/frontend/lowering/builtin-calls.ts @@ -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; diff --git a/packages/compiler/src/frontend/lowering/builtins/performance.ts b/packages/compiler/src/frontend/lowering/builtins/performance.ts index 19b585e37..7aac3b80c 100644 --- a/packages/compiler/src/frontend/lowering/builtins/performance.ts +++ b/packages/compiler/src/frontend/lowering/builtins/performance.ts @@ -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 }; diff --git a/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts b/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts index 14cc8ba86..ed0f1f91c 100644 --- a/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts +++ b/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts @@ -322,7 +322,9 @@ export function funcCoerceAdapter( ), ]; } else if (checkedRet) { - body = [{ kind: "return", value: { kind: "dynCheck", value: call, type: toT.ret, loc }, loc }]; + body = [ + { kind: "return", value: { kind: "dynCheck", value: call, type: toT.ret, loc }, loc }, + ]; } else { const result = lowerer.coerceToExpected(call, toT.ret); if (!typeEquals(result.type, toT.ret)) diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 14d5d71d3..f12fcf99a 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -144,7 +144,6 @@ import { fencePerfEntryKeyWalk, isPerfEntryType, lowerHistogramMethodCall, - lowerMonitorEventLoopDelayCall, lowerPerfHooksCall, perfEntryTag, } from "./builtins/performance.js"; @@ -243,16 +242,6 @@ const ZLIB_STREAM_METHODS: ReadonlySet = 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 ( diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 61fc0f8d0..a84e37180 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -13835,6 +13835,24 @@ const OBJECT_PROTOTYPE_KEYS: ReadonlySet = new Set([ * idiom over multiple shapes), index-signature keys, class instances, * and dyn/unknown stay fenced. Keys are literal strings — a computed key * over a shape would need the runtime key table. */ +/** 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 = new Set([ + "errno", + "syscall", + "path", + "dest", + "info", + "errors", + "error", + "suppressed", + "actual", + "expected", + "operator", + "generatedMessage", +]); + function lowerInExpression(lowerer: Lowerer, expr: ts.BinaryExpression, loc: SrcLoc): IrExpr { if ( lowerer.isStdlibGlobal(expr.right, "process") && @@ -14018,9 +14036,10 @@ function lowerInExpression(lowerer: Lowerer, expr: ts.BinaryExpression, loc: Src // predicate's `err instanceof Error && "code" in err` shape): `code` // answers from the runtime error's code slot (stamped by fs/system/ // event/spawn errors, absent on plain constructions — exactly Node's - // own-property answer), and the always-declared members fold true. - // Everything else fences: the runtime object carries no other dynamic - // properties to ask (`stack` would be a lie — Node has one, we don't). + // own-property answer), and the always-present members (message, name, + // stack, Object.prototype's keys, members the static class declares) + // fold true. Other keys test the declaring subclasses and the error's + // own runtime properties; keys of built-in errors' extra slots fence. if (recv.type.kind === "object") { // %DOMException first: `'cause' in e` answers the options form's // own-property record (the runtime slot), and code/name/message are @@ -14076,6 +14095,9 @@ function lowerInExpression(lowerer: Lowerer, expr: ts.BinaryExpression, loc: Src 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) || @@ -14084,12 +14106,12 @@ function lowerInExpression(lowerer: Lowerer, expr: ts.BinaryExpression, loc: Src 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); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index e6c6720e0..cf33d9f2e 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1350,7 +1350,8 @@ function lowerLabeled(lowerer: Lowerer, stmt: ts.LabeledStatement): IrStmt | IrS } export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt[] | null { - if (ownPrototypeResetStatement(lowerer, stmt)) return { kind: "block", body: [], loc: locOf(stmt) }; + if (ownPrototypeResetStatement(lowerer, stmt)) + return { kind: "block", body: [], loc: locOf(stmt) }; if (ts.isClassDeclaration(stmt)) { if (!stmt.name) lowerer.unsupported("SC1090", stmt, "anonymous local class declarations"); const info = lowerer.lowerClassExpressionInfo(stmt); @@ -10924,7 +10925,13 @@ function resetTarget(lowerer: Lowerer, expr: ts.Expression): ts.Expression | nul function enclosingConstructor(node: ts.Node): ts.ConstructorDeclaration | null { for (let cur = node.parent; cur !== undefined; cur = cur.parent) { if (ts.isConstructorDeclaration(cur)) return cur; - if (ts.isFunctionLike(cur) || (ts.isClassDeclaration(cur) || ts.isClassExpression(cur)) || ts.isSourceFile(cur)) return null; + if ( + ts.isFunctionLike(cur) || + ts.isClassDeclaration(cur) || + ts.isClassExpression(cur) || + ts.isSourceFile(cur) + ) + return null; } return null; } @@ -10947,7 +10954,8 @@ function ownPrototypeRef( ) return true; const cls = ctor.parent; - if (!ts.isIdentifier(base) || !(ts.isClassDeclaration(cls) || ts.isClassExpression(cls))) return false; + if (!ts.isIdentifier(base) || !(ts.isClassDeclaration(cls) || ts.isClassExpression(cls))) + return false; const symbol = lowerer.resolveValueSymbol(base); const info = symbol ? lowerer.classBySymbol.get(symbol) : undefined; if (!info || lowerer.checker.valueDeclarationOf(symbol!) !== cls) return false; diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 2d083d0cb..89a24f3d6 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -5146,11 +5146,15 @@ function mapGenericIndexedAccess(type: ts.Type, ctx: TypeMapperCtx): IrType | nu const obj = type.getObjectType(); if (obj === undefined || !(obj.flags & ts.TypeFlags.TypeParameter)) return null; const bound = resolveTypeParam(obj); + const idx = type.getIndexType(); + if (idx === undefined) return null; + // `T[keyof T]` over a T bound to an unknown-valued record view + // (`Record` maps to the checked-dynamic value, see + // mapRecordType): every member reads as unknown. + if (bound?.kind === "dyn" && idx.isIndexType() && idx.getTarget() === obj) return DYN; if (!bound || bound.kind !== "record") return null; const shape = shapes.get(bound.shapeId); if (!shape || shape.tuple) return null; - const idx = type.getIndexType(); - if (idx === undefined) return null; let covered: IrType[] | null = null; if (idx.isStringLiteralType()) { const f = shape.fields.find((x) => x.name === idx.value); @@ -5206,6 +5210,17 @@ function mapBoundKeyofNeverConditional(type: ts.Type, ctx: TypeMapperCtx): IrTyp const target = check.getTarget(); if (!target || !(target.flags & ts.TypeFlags.TypeParameter)) return null; const bound = resolveTypeParam(target); + // A T bound to the checked-dynamic value (`Record`, an + // unknown-valued record view) has no static key set to decide the + // branch, but both branches of the lib's `keyof T extends never ? + // unknown : T[keyof T]` read as unknown there: one answer either way. + if (bound?.kind === "dyn") { + const whenEmpty = type.getTrueType(); + const otherwise = type.getFalseType(); + const a = whenEmpty ? mapType(whenEmpty, ctx) : null; + const b = otherwise ? mapType(otherwise, ctx) : null; + return a && b && typeEquals(a, b) ? a : null; + } if (bound?.kind !== "record") return null; const shape = shapes.get(bound.shapeId); if (!shape || shape.tuple) return null; diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index f2041a6aa..350b44273 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -363,6 +363,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = 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", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 34f9da161..57ecbc8d4 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -364,7 +364,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 }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index b2628fa10..1946faeb3 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -506,11 +506,11 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run } if (fn.startsWith("dgram.") || fn.startsWith("dns.")) features.dgram = true; if (fn.startsWith("tlsca.")) features.tlsCa = true; + if (fn.startsWith("perf.loop") || fn === "perf.idleTime" || fn.startsWith("perf.eld")) + features.perfLoop = true; // scr_cipher.c over the mbedTLS archive; DEP0182 reports through the // process-warning unit, and unknown output encodings render through // the inspect unit. - if (fn.startsWith("perf.loop") || fn === "perf.idleTime" || fn.startsWith("perf.eld")) - features.perfLoop = true; if (fn === "crypto.cipherNew") { features.cipher = true; features.dynAsync = true; diff --git a/packages/compiler/src/library/ir-policy.ts b/packages/compiler/src/library/ir-policy.ts index 3c3855e5c..a9c9609f5 100644 --- a/packages/compiler/src/library/ir-policy.ts +++ b/packages/compiler/src/library/ir-policy.ts @@ -53,6 +53,11 @@ const LIB_MODE_REFUSED_PREFIXES: readonly [string, string][] = [ ["crypto.cipherNew", "crypto.createCipheriv and createDecipheriv"], ["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"], @@ -157,6 +162,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 }; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-wrapper-untyped-results.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-wrapper-untyped-results.ts.json new file mode 100644 index 000000000..20c1be33a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-wrapper-untyped-results.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6410-wrapper-untyped-results.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-prototype-reset-constructors/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-prototype-reset-constructors/main.ts.json new file mode 100644 index 000000000..adfb3989d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-prototype-reset-constructors/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6411-prototype-reset-constructors/issues.js", + "/tests/corpus/6411-prototype-reset-constructors/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json new file mode 100644 index 000000000..0b4118c70 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6468-generic-unknown-record-entries.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_perf_loop.c b/packages/runtime/src/scr_perf_loop.c index 29d98ea29..68d3bf10f 100644 --- a/packages/runtime/src/scr_perf_loop.c +++ b/packages/runtime/src/scr_perf_loop.c @@ -21,7 +21,9 @@ #include #include +#include #include +#include #include #ifdef _WIN32 #include @@ -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; diff --git a/tests/corpus/6394-in-key-error-subclasses.ts b/tests/corpus/6394-in-key-error-subclasses.ts index cdb28a5a3..5ade6bcd1 100644 --- a/tests/corpus/6394-in-key-error-subclasses.ts +++ b/tests/corpus/6394-in-key-error-subclasses.ts @@ -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"))); diff --git a/tests/corpus/6397-event-loop-metrics.ts b/tests/corpus/6397-event-loop-metrics.ts index e1c3829ac..8477dcc48 100644 --- a/tests/corpus/6397-event-loop-metrics.ts +++ b/tests/corpus/6397-event-loop-metrics.ts @@ -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()); diff --git a/tests/corpus/6468-generic-unknown-record-entries.ts b/tests/corpus/6468-generic-unknown-record-entries.ts new file mode 100644 index 000000000..f0e57756a --- /dev/null +++ b/tests/corpus/6468-generic-unknown-record-entries.ts @@ -0,0 +1,33 @@ +// Inside a generic body, `Object.entries(tags)` over `T extends +// Record` reads `[string, T[keyof T]]` elements. An +// instance whose T is the unknown-valued record view reads every member +// as unknown; a record instance reads the union of its member types. +function upper(text: string): string { + return text.toUpperCase(); +} + +function scrub(text: string): string { + return text.replace(/secret/g, "***"); +} + +function describe>(tags: T): string[] { + return Object.entries(tags).map( + ([key, value]) => `${key}=${typeof value === "string" ? upper(value) : String(value)}`, + ); +} + +function redact>(tags: T): T { + return Object.fromEntries( + Object.entries(tags).map(([key, value]) => [key, typeof value === "string" ? scrub(value) : value]), + ) as T; +} + +function flatten(value: unknown): Record { + return typeof value === "object" && value !== null ? (value as Record) : { value }; +} + +console.log(describe(flatten({ b: 1, a: "x", c: true }))); +console.log(describe(flatten(5))); +console.log(describe({ name: "n", size: 3 })); +const cleaned = redact(flatten({ token: "secret", count: 2 })); +console.log(cleaned, Object.keys(cleaned)); diff --git a/tests/harness/corpus-costs/6400-override-any-base.ts.json b/tests/harness/corpus-costs/6400-override-any-base.ts.json new file mode 100644 index 000000000..5ff4195cc --- /dev/null +++ b/tests/harness/corpus-costs/6400-override-any-base.ts.json @@ -0,0 +1 @@ +{"plain":4713} diff --git a/tests/harness/corpus-costs/6401-override-param-variance.ts.json b/tests/harness/corpus-costs/6401-override-param-variance.ts.json new file mode 100644 index 000000000..0f324b571 --- /dev/null +++ b/tests/harness/corpus-costs/6401-override-param-variance.ts.json @@ -0,0 +1 @@ +{"plain":3445} diff --git a/tests/harness/corpus-costs/6402-override-js-base/main.ts.json b/tests/harness/corpus-costs/6402-override-js-base/main.ts.json new file mode 100644 index 000000000..dafa7b34e --- /dev/null +++ b/tests/harness/corpus-costs/6402-override-js-base/main.ts.json @@ -0,0 +1 @@ +{"plain":5309} diff --git a/tests/harness/corpus-costs/6403-error-code-write.ts.json b/tests/harness/corpus-costs/6403-error-code-write.ts.json new file mode 100644 index 000000000..e4d08c9bb --- /dev/null +++ b/tests/harness/corpus-costs/6403-error-code-write.ts.json @@ -0,0 +1 @@ +{"plain":3632} diff --git a/tests/harness/corpus-costs/6404-error-in-and-assign.ts.json b/tests/harness/corpus-costs/6404-error-in-and-assign.ts.json new file mode 100644 index 000000000..12a27db7d --- /dev/null +++ b/tests/harness/corpus-costs/6404-error-in-and-assign.ts.json @@ -0,0 +1 @@ +{"plain":4787} diff --git a/tests/harness/corpus-costs/6405-wrapped-default-parameters.ts.json b/tests/harness/corpus-costs/6405-wrapped-default-parameters.ts.json new file mode 100644 index 000000000..2fe355bec --- /dev/null +++ b/tests/harness/corpus-costs/6405-wrapped-default-parameters.ts.json @@ -0,0 +1 @@ +{"plain":4127} diff --git a/tests/harness/corpus-costs/6406-npm-instance-receiver/main.ts.json b/tests/harness/corpus-costs/6406-npm-instance-receiver/main.ts.json new file mode 100644 index 000000000..0e304e2ed --- /dev/null +++ b/tests/harness/corpus-costs/6406-npm-instance-receiver/main.ts.json @@ -0,0 +1 @@ +{"plain":4693} diff --git a/tests/harness/corpus-costs/6407-js-error-tostring-override/main.ts.json b/tests/harness/corpus-costs/6407-js-error-tostring-override/main.ts.json new file mode 100644 index 000000000..64c9f2e6d --- /dev/null +++ b/tests/harness/corpus-costs/6407-js-error-tostring-override/main.ts.json @@ -0,0 +1 @@ +{"plain":3803} diff --git a/tests/harness/corpus-costs/6408-override-js-async-base/main.ts.json b/tests/harness/corpus-costs/6408-override-js-async-base/main.ts.json new file mode 100644 index 000000000..eeb8f92e9 --- /dev/null +++ b/tests/harness/corpus-costs/6408-override-js-async-base/main.ts.json @@ -0,0 +1 @@ +{"plain":3820} diff --git a/tests/harness/corpus-costs/6409-catch-binding-shapes.ts.json b/tests/harness/corpus-costs/6409-catch-binding-shapes.ts.json new file mode 100644 index 000000000..072cdc179 --- /dev/null +++ b/tests/harness/corpus-costs/6409-catch-binding-shapes.ts.json @@ -0,0 +1 @@ +{"plain":3726} diff --git a/tests/harness/corpus-costs/6410-wrapper-untyped-results.ts.json b/tests/harness/corpus-costs/6410-wrapper-untyped-results.ts.json new file mode 100644 index 000000000..970974165 --- /dev/null +++ b/tests/harness/corpus-costs/6410-wrapper-untyped-results.ts.json @@ -0,0 +1 @@ +{"plain":3837} diff --git a/tests/harness/corpus-costs/6411-prototype-reset-constructors/main.ts.json b/tests/harness/corpus-costs/6411-prototype-reset-constructors/main.ts.json new file mode 100644 index 000000000..b2731b1c1 --- /dev/null +++ b/tests/harness/corpus-costs/6411-prototype-reset-constructors/main.ts.json @@ -0,0 +1 @@ +{"plain":4711} diff --git a/tests/harness/library-asyncfree.test.ts b/tests/harness/library-asyncfree.test.ts index 0adbef7bc..7c4d029cc 100644 --- a/tests/harness/library-asyncfree.test.ts +++ b/tests/harness/library-asyncfree.test.ts @@ -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( diff --git a/tests/harness/library-profile.test.ts b/tests/harness/library-profile.test.ts index c7cb13751..c83b31ada 100644 --- a/tests/harness/library-profile.test.ts +++ b/tests/harness/library-profile.test.ts @@ -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",