mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Support non-Error 'error' payloads and checked-dynamic listeners in listeners() and meta events
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
- **EventEmitter:** Literal event names use fixed argument tuples.
|
||||
Computed string names require a constant prefix or suffix that excludes `error`, meta events, and stream-internal events; their arguments must match the exact arity and support checked conversion.
|
||||
Unrestricted names, symbols, names that may reach internal events, and typed `listeners()`/`rawListeners()` results for names with possible computed registrations are unsupported.
|
||||
Unrestricted names, symbols, and names that may reach internal events are unsupported.
|
||||
An emitter reached through an untyped value shares one listener list with its typed uses, so untyped registrations and emits reach typed listeners and typed emits reach untyped listeners.
|
||||
An untyped `emit` of an event that has a listener with typed parameters converts its arguments to the event's typed tuple before any listener runs, and throws a `TypeError` when they do not convert.
|
||||
Through an untyped value, `listeners()` and `rawListeners()` of an event with a listener that has typed parameters are unsupported, and `emit('error')` of an object that is not an Error with no `error` listener is unsupported.
|
||||
Untyped `newListener` and `removeListener` listeners receive `undefined` in place of a listener registered with typed parameters.
|
||||
A `newListener` or `removeListener` listener registered through an untyped value receives `undefined` in place of a listener registered with typed parameters.
|
||||
A typed `error` or `errorMonitor` listener throws a `TypeError` when an emitted payload that is not an Error does not convert to its parameter.
|
||||
Reading EventEmitter methods as properties through an untyped value (`typeof emitter.listeners`) answers `undefined`.
|
||||
`events.once` accepts EventEmitter and stream instances with a literal event name and an optional object-literal `{ signal }`; a stream's `data` event, other options values, and EventTarget emitters are unsupported.
|
||||
Reading `defaultMaxListeners` through a CommonJS destructured or member binding (`const { defaultMaxListeners } = require("events")`) is unsupported.
|
||||
|
||||
@@ -951,6 +951,34 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
host.emitPendingCheck();
|
||||
return { name: t, type: e.type };
|
||||
}
|
||||
if (e.fn === "emitter.emitErrorValue") {
|
||||
// (recv, ...payload as dyn): emit('error') with a non-Error payload.
|
||||
// The unhandled case renders the payload with util.inspect
|
||||
// (scr_insp_dyn), which this call links.
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
const n = args.length - 1;
|
||||
let payload = "null";
|
||||
if (n > 0) {
|
||||
const arr = B.slot();
|
||||
B.entryAllocas.push(`${arr} = alloca [${n} x ptr]`);
|
||||
for (let i = 0; i < n; i++) {
|
||||
const slot = B.tmp();
|
||||
B.line(`${slot} = getelementptr inbounds [${n} x ptr], ptr ${arr}, i64 0, i64 ${i}`);
|
||||
B.line(`store ptr ${args[i + 1]!.name}, ptr ${slot}`);
|
||||
}
|
||||
payload = arr;
|
||||
}
|
||||
host.declare(`declare ptr @scr_insp_dyn(ptr, double, double)`);
|
||||
host.declare(
|
||||
`declare zeroext i1 @scr_emitter_emit_error_value(ptr, ptr, ${host.sizeType}, ptr)`,
|
||||
);
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
`${t} = call i1 @scr_emitter_emit_error_value(ptr ${args[0]!.name}, ptr ${payload}, ${host.sizeType} ${n}, ptr @scr_insp_dyn)`,
|
||||
);
|
||||
host.emitPendingCheck();
|
||||
return { name: t, type: e.type };
|
||||
}
|
||||
if (e.fn === "emitter.emitFlex" || e.fn === "emitter.emitAny") {
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
const n = args.length - 2;
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
* super.emit as the prototype chain). */
|
||||
import { keyedHelperName } from "./stable-names.js";
|
||||
import { checkKeypressListener } from "./builtins/readline.js";
|
||||
import { stripTypeCasts } from "./builtins/arguments.js";
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import { dynFallbackType, newFnCtx } from "./lowerer.js";
|
||||
@@ -120,6 +121,54 @@ export interface EventSig {
|
||||
unmappedEmit?: true;
|
||||
/** A listener with any concrete parameter still needs the fixed ABI. */
|
||||
typedListener?: true;
|
||||
/** listeners(), rawListeners() or events.getEventListeners() reads the
|
||||
* event's listener array. With a dyn-flavored listener present, every
|
||||
* registration of the event goes through the dyn adapter, so the array
|
||||
* holds checked-dynamic function values (listenerArrayIsDyn). */
|
||||
listenersRead?: true;
|
||||
/** ('error' only) An emit passes a payload that is not statically an
|
||||
* Error, which typed listeners of 'error' and errorMonitor receive as a
|
||||
* checked-dynamic value. */
|
||||
valueEmit?: true;
|
||||
}
|
||||
|
||||
/** Whether `t` is an instance type of the Error hierarchy. */
|
||||
function rootsAtError(lowerer: Lowerer, t: IrType | null): boolean {
|
||||
if (t?.kind !== "object") return false;
|
||||
for (let c: ClassInfo | null = lowerer.classes.get(t.className) ?? null; c; c = c.base) {
|
||||
if (c.def.name === "%Error") return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Whether a typed listener of `name` registers through the dyn adapter, so
|
||||
* its entry holds a checked-dynamic function value: a dyn-flavored
|
||||
* 'newListener' or 'removeListener' listener receives every added or
|
||||
* removed listener as one, and a read listener array beside dyn-flavored
|
||||
* listeners holds them. Listeners of 'error' and errorMonitor do when an
|
||||
* emit passes a payload that is not an Error: the adapter's checked
|
||||
* conversion admits it or throws a TypeError. */
|
||||
function registersAsDyn(lowerer: Lowerer, name: string, streamForced: boolean): boolean {
|
||||
const table = emitterEvents(lowerer);
|
||||
if ([...META_EVENTS].some((meta) => table.get(meta)?.dynListener === true)) return true;
|
||||
if ((name === "error" || name === ERROR_MONITOR_EVENT) && table.get("error")?.valueEmit === true)
|
||||
return true;
|
||||
const sig = streamForced ? undefined : table.get(name);
|
||||
return sig?.dynListener === true && sig.listenersRead === true;
|
||||
}
|
||||
|
||||
/** Whether the event's listener array is read as checked-dynamic values:
|
||||
* its registrations all hold a checked-dynamic function value (flexible
|
||||
* and computed-name events, or a dyn-flavored listener beside a read of
|
||||
* the array). */
|
||||
function listenerArrayIsDyn(lowerer: Lowerer, name: string, sig: EventSig | undefined): boolean {
|
||||
return (
|
||||
(sig?.dynListener === true && sig.listenersRead === true) ||
|
||||
flexibleJsEvent(name, sig) ||
|
||||
computedPatterns(lowerer).some(
|
||||
(pattern) => pattern.listener && patternMatchesName(pattern, name),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function flexibleJsEvent(name: string, sig: EventSig | undefined): boolean {
|
||||
@@ -286,12 +335,48 @@ function emitterEvents(lowerer: Lowerer): Map<string, EventSig> {
|
||||
}
|
||||
};
|
||||
|
||||
// Events whose listener array is read; applied to the table after the
|
||||
// walk, so a read alone adds no entry.
|
||||
const listenersRead = new Set<string>();
|
||||
for (const sf of lowerer.program.getImplementationSourceFiles()) {
|
||||
if (sf.isDeclarationFile) continue;
|
||||
const walk = (node: ts.Node): void => {
|
||||
ts.forEachChild(node, walk);
|
||||
if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) return;
|
||||
const member = node.expression.name.text;
|
||||
if (!ts.isCallExpression(node)) return;
|
||||
const callee = node.expression;
|
||||
if (
|
||||
(ts.isIdentifier(callee) || ts.isPropertyAccessExpression(callee)) &&
|
||||
(ts.isIdentifier(callee) ? callee : callee.name).text === "getEventListeners" &&
|
||||
node.arguments.nodes.length === 2
|
||||
) {
|
||||
const nameT = (() => {
|
||||
try {
|
||||
return lowerer.typeOf(node.arguments.nodes[1]!);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
if (nameT?.isStringLiteralType()) listenersRead.add(literalEventKey(nameT.value));
|
||||
return;
|
||||
}
|
||||
if (!ts.isPropertyAccessExpression(callee)) return;
|
||||
const member = callee.name.text;
|
||||
if (member === "listeners" || member === "rawListeners") {
|
||||
const arg = node.arguments.nodes[0];
|
||||
let nameT: ts.Type | null = null;
|
||||
try {
|
||||
if (
|
||||
!arg ||
|
||||
!scannedEmitterInfo(lowerer, lowerer.mapTypeOf(lowerer.typeOf(callee.expression)))
|
||||
)
|
||||
return;
|
||||
nameT = lowerer.typeOf(arg);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (nameT.isStringLiteralType()) listenersRead.add(literalEventKey(nameT.value));
|
||||
return;
|
||||
}
|
||||
const isEmit = member === "emit";
|
||||
if (!isEmit && !REGISTER_MEMBERS.has(member)) return;
|
||||
const arg0 = node.arguments.nodes[0];
|
||||
@@ -299,7 +384,7 @@ function emitterEvents(lowerer: Lowerer): Map<string, EventSig> {
|
||||
let recvT: IrType | null = null;
|
||||
let nameT: ts.Type | null = null;
|
||||
try {
|
||||
recvT = lowerer.mapTypeOf(lowerer.typeOf(node.expression.expression));
|
||||
recvT = lowerer.mapTypeOf(lowerer.typeOf(callee.expression));
|
||||
nameT = lowerer.typeOf(arg0);
|
||||
} catch {
|
||||
return; // checker trouble is the lowering's business, not the scan's
|
||||
@@ -323,6 +408,46 @@ function emitterEvents(lowerer: Lowerer): Map<string, EventSig> {
|
||||
return;
|
||||
}
|
||||
const name = literal;
|
||||
if (META_EVENTS.has(name) && !isEmit && node.arguments.nodes[1]) {
|
||||
// A dyn-flavored meta listener receives each added or removed
|
||||
// listener as a checked-dynamic function value. The scan cannot
|
||||
// lower the listener as dynListenerFlavor does, so it counts every
|
||||
// listener that is not statically a typed function: a factory's
|
||||
// result (a wrapper whose runtime value is checked-dynamic), a
|
||||
// JavaScript function without parameters, and the dyn-flavored
|
||||
// forms. Counting one too many only routes more registrations
|
||||
// through the adapter.
|
||||
const cbNode = node.arguments.nodes[1];
|
||||
let typed = false;
|
||||
try {
|
||||
const cbCt = lowerer.typeOf(cbNode);
|
||||
const cbT = lowerer.mapTypeOf(cbCt) ?? dynFallbackType(lowerer, cbNode, cbCt);
|
||||
typed =
|
||||
cbT?.kind === "func" &&
|
||||
!ts.isCallExpression(stripTypeCasts(cbNode)) &&
|
||||
!(cbT.params.length === 0 && isJsSourceFile(sf)) &&
|
||||
!cbT.params.some((p) => p.kind === "dyn") &&
|
||||
!readsArgumentsPack(lowerer, cbNode);
|
||||
} catch {
|
||||
typed = false;
|
||||
}
|
||||
if (!typed) table.get(name)!.dynListener = true;
|
||||
return;
|
||||
}
|
||||
if (isEmit && name === "error") {
|
||||
try {
|
||||
const payload = node.arguments.nodes.slice(1);
|
||||
if (
|
||||
payload.length !== 1 ||
|
||||
!rootsAtError(lowerer, lowerer.mapTypeOf(lowerer.typeOf(payload[0]!)))
|
||||
) {
|
||||
table.get("error")!.valueEmit = true;
|
||||
}
|
||||
} catch {
|
||||
table.get("error")!.valueEmit = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (META_EVENTS.has(name) || name === "error" || name === ERROR_MONITOR_EVENT) return; // forced tuples
|
||||
// Stream receivers' runtime-emitted events carry PER-BASE forced
|
||||
// tuples (lower-stream.ts) — their sites never join the program-
|
||||
@@ -377,6 +502,10 @@ function emitterEvents(lowerer: Lowerer): Map<string, EventSig> {
|
||||
};
|
||||
walk(sf);
|
||||
}
|
||||
for (const name of listenersRead) {
|
||||
const sig = table.get(name);
|
||||
if (sig) sig.listenersRead = true;
|
||||
}
|
||||
lowerer.emitterEventTable = table;
|
||||
lowerer.emitterComputedPatterns = computedPatterns;
|
||||
return table;
|
||||
@@ -604,6 +733,7 @@ function lowerFlexListenerCall(
|
||||
once: boolean,
|
||||
prepend: boolean,
|
||||
loc: SrcLoc,
|
||||
onFn: "emitter.onFlex" | "emitter.onAny" = "emitter.onFlex",
|
||||
): IrExpr {
|
||||
if (receiver.type.kind !== "object") {
|
||||
lowerer.noLowering(
|
||||
@@ -633,10 +763,10 @@ function lowerFlexListenerCall(
|
||||
lowerer.noLowering(`${member} with a non-function computed-name listener`, cbNode);
|
||||
}
|
||||
const recvT = receiver.type;
|
||||
const key = `emitter.${registering ? "onFlex" : "offDyn"}:${typeKey(recvT)}`;
|
||||
const key = `${registering ? onFn : "emitter.offDyn"}:${typeKey(recvT)}`;
|
||||
let helper = lowerer.arrHofHelpers.get(key);
|
||||
if (!helper) {
|
||||
helper = keyedHelperName(lowerer, `%emitter.${registering ? "onFlex" : "offFlex"}`, key);
|
||||
helper = keyedHelperName(lowerer, `%${registering ? onFn : "emitter.offFlex"}`, key);
|
||||
lowerer.arrHofHelpers.set(key, helper);
|
||||
const params: IrParam[] = [
|
||||
{ localId: "r.0", name: "r", type: recvT },
|
||||
@@ -671,7 +801,7 @@ function lowerFlexListenerCall(
|
||||
kind: "return",
|
||||
value: {
|
||||
kind: "libCall",
|
||||
fn: registering ? "emitter.onFlex" : "emitter.offDyn",
|
||||
fn: registering ? onFn : "emitter.offDyn",
|
||||
args: [
|
||||
varRef("r.0", recvT, loc),
|
||||
varRef("n.0", STRING, loc),
|
||||
@@ -699,6 +829,59 @@ function lowerFlexListenerCall(
|
||||
loc,
|
||||
};
|
||||
}
|
||||
/** A lowered value as a checked-dynamic payload: boxed as any untyped
|
||||
* value is (records and arrays keep their identity), otherwise a refusal
|
||||
* at the argument. */
|
||||
function boxedPayload(lowerer: Lowerer, value: IrExpr, node: ts.Expression): IrExpr {
|
||||
if (value.type.kind === "dyn") return value;
|
||||
if (
|
||||
value.kind === "unitLit" ||
|
||||
canConvertToDyn(
|
||||
value.type,
|
||||
(id) => lowerer.shapes.get(id),
|
||||
(id) => lowerer.unions.get(id),
|
||||
)
|
||||
) {
|
||||
return lowerer.coerceToExpected(value, DYN);
|
||||
}
|
||||
lowerer.noLowering(
|
||||
`emit('error') with a '${lowerer.fmt(value.type)}' payload`,
|
||||
node,
|
||||
"the payload cannot box into a dynamic value",
|
||||
);
|
||||
}
|
||||
|
||||
/** The listener array of an event whose registrations all hold a checked-
|
||||
* dynamic function value (listenerArrayIsDyn), each comparing equal to the
|
||||
* function that was registered. JavaScript sources read the untyped array,
|
||||
* whose functions take every argument a call passes. TypeScript sources
|
||||
* read the element type the typed path declares, the event's tuple: a
|
||||
* listener of exactly that signature unwraps to the original closure. */
|
||||
function dynListenerArray(
|
||||
lowerer: Lowerer,
|
||||
table: Map<string, EventSig>,
|
||||
receiver: IrExpr,
|
||||
name: string,
|
||||
call: ts.CallExpression,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const array: IrExpr = {
|
||||
kind: "libCall",
|
||||
fn: "emitter.listenersDyn",
|
||||
args: [receiver, eventNameExpr(name, loc)],
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
if (isJsSourceFile(ts.getSourceFileOfNode(call))) return array;
|
||||
const tuple = tupleOf(lowerer, table, name, call);
|
||||
return {
|
||||
kind: "dynCheck",
|
||||
value: array,
|
||||
type: arrayOf({ kind: "func", params: tuple, ret: { kind: "void" } }),
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** The event's unified tuple, with conflicts reported at this site. */
|
||||
function tupleOf(
|
||||
lowerer: Lowerer,
|
||||
@@ -820,10 +1003,29 @@ function lowerDynListenerCall(
|
||||
streamData: boolean,
|
||||
): IrExpr {
|
||||
if (META_EVENTS.has(name)) {
|
||||
lowerer.noLowering(
|
||||
`'${member}' of '${name}' with a checked-dynamic listener`,
|
||||
// The registration installs the registry's dynamic half: the listener
|
||||
// receives the event name and the added or removed listener's
|
||||
// checked-dynamic function value, as in Node. Every other listener
|
||||
// then registers through the adapter (registersAsDyn), which the event
|
||||
// table's scan arranges.
|
||||
if (registering && emitterEvents(lowerer).get(name)?.dynListener !== true) {
|
||||
lowerer.noLowering(
|
||||
`'${member}' of '${name}' with a checked-dynamic listener the event table did not record`,
|
||||
cbNode,
|
||||
"register the listener directly at this call",
|
||||
);
|
||||
}
|
||||
return lowerFlexListenerCall(
|
||||
lowerer,
|
||||
member,
|
||||
eventNameExpr(name, loc),
|
||||
receiver,
|
||||
cbNode,
|
||||
"Node passes the listener FUNCTION as the meta event's second argument, which has no dynamic conversion — annotate the listener's parameter as a string",
|
||||
registering,
|
||||
once,
|
||||
prepend,
|
||||
loc,
|
||||
"emitter.onAny",
|
||||
);
|
||||
}
|
||||
const getRecord = (id: string) => lowerer.shapes.get(id);
|
||||
@@ -1490,7 +1692,9 @@ export function lowerEmitterMethodCall(
|
||||
// to the declared type, or take the value (dyn).
|
||||
const sides = streamSidesOf(lowerer, info);
|
||||
if (name === "data" && (sides === "r" || sides === "rw")) {
|
||||
const dynFlavor = dynListenerFlavor(lowerer, args[1]!);
|
||||
const dynFlavor =
|
||||
dynListenerFlavor(lowerer, args[1]!) ??
|
||||
(registersAsDyn(lowerer, name, true) ? "func" : null);
|
||||
if (dynFlavor !== null) {
|
||||
return lowerDynListenerCall(
|
||||
lowerer,
|
||||
@@ -1575,7 +1779,11 @@ export function lowerEmitterMethodCall(
|
||||
const probe = tryLowerExpression(lowerer, args[1]!);
|
||||
if (probe) checkKeypressListener(lowerer, args[1]!, probe);
|
||||
}
|
||||
const dynFlavor = dynListenerFlavor(lowerer, args[1]!) ?? (keypress ? "func" : null);
|
||||
const dynFlavor =
|
||||
dynListenerFlavor(lowerer, args[1]!) ??
|
||||
(keypress || registersAsDyn(lowerer, name, streamForcedTuple(lowerer, info, name) !== null)
|
||||
? "func"
|
||||
: null);
|
||||
if (dynFlavor !== null) {
|
||||
return lowerDynListenerCall(
|
||||
lowerer,
|
||||
@@ -1654,29 +1862,36 @@ export function lowerEmitterMethodCall(
|
||||
);
|
||||
const receiver = lowerReceiver();
|
||||
if (name === "error") {
|
||||
// The special event: exactly one %Error-rooted payload; no listener
|
||||
// means the runtime THROWS it (emitter.emitError, may-throw).
|
||||
if (args.length !== 2) {
|
||||
lowerer.noLowering(
|
||||
`emit('error') with ${args.length - 1} payload arguments`,
|
||||
call,
|
||||
"the supported form is emit('error', err) with an Error-hierarchy payload (non-Error payloads would need Node's ERR_UNHANDLED_ERROR wrapping — no lowering yet)",
|
||||
);
|
||||
}
|
||||
const err = lowerer.lowerExpr(args[1]!);
|
||||
const rootsAtError = (t: IrType): boolean => {
|
||||
if (t.kind !== "object") return false;
|
||||
for (let c: ClassInfo | null = lowerer.classes.get(t.className) ?? null; c; c = c.base) {
|
||||
if (c.def.name === "%Error") return true;
|
||||
// The special event: one %Error-rooted payload; no listener means the
|
||||
// runtime THROWS it (emitter.emitError, may-throw).
|
||||
const err = args.length === 2 ? lowerer.lowerExpr(args[1]!) : null;
|
||||
if (err === null || !rootsAtError(lowerer, err.type)) {
|
||||
// Any other payload, or another argument count: the arguments box
|
||||
// to dyn. With no 'error' listener the runtime throws Node's
|
||||
// ERR_UNHANDLED_ERROR (the payload's util.inspect in the message,
|
||||
// the payload as `context`); listeners receive the arguments as
|
||||
// given.
|
||||
if (emitOverrideAtOrAbove(info) || superRecv !== undefined) {
|
||||
lowerer.noLowering(
|
||||
`emit('error') with a non-Error payload through an emit override`,
|
||||
call,
|
||||
"the override specialization takes the event's one Error payload",
|
||||
);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!rootsAtError(err.type)) {
|
||||
lowerer.noLowering(
|
||||
`emit('error') with a '${lowerer.fmt(err.type)}' payload`,
|
||||
args[1]!,
|
||||
"the supported payload is an Error-hierarchy instance",
|
||||
);
|
||||
const payload = args
|
||||
.slice(1)
|
||||
.map((arg, i) =>
|
||||
i === 0 && err !== null
|
||||
? boxedPayload(lowerer, err, arg)
|
||||
: lowerer.lowerExprExpecting(arg, DYN),
|
||||
);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "emitter.emitErrorValue",
|
||||
args: [receiver, ...payload],
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
return emitDispatchExpr(
|
||||
lowerer,
|
||||
@@ -1824,13 +2039,10 @@ export function lowerEmitterMethodCall(
|
||||
}
|
||||
const name = eventNameOf(lowerer, member, args[0]!);
|
||||
const sig = table.get(name);
|
||||
if (
|
||||
sig?.dynListener ||
|
||||
flexibleJsEvent(name, sig) ||
|
||||
computedPatterns(lowerer).some(
|
||||
(pattern) => pattern.listener && patternMatchesName(pattern, name),
|
||||
)
|
||||
) {
|
||||
if (listenerArrayIsDyn(lowerer, name, sig)) {
|
||||
return dynListenerArray(lowerer, table, lowerReceiver(), name, call, loc);
|
||||
}
|
||||
if (sig?.dynListener) {
|
||||
// A dyn-adapted registration means the runtime bucket can hold
|
||||
// originals of MIXED signatures — no one honest element type.
|
||||
lowerer.noLowering(
|
||||
@@ -2809,13 +3021,10 @@ export function lowerEventsEmitterHelper(
|
||||
const name = eventNameOf(lowerer, "events.getEventListeners", args[1]!);
|
||||
const table = emitterEvents(lowerer);
|
||||
const sig = table.get(name);
|
||||
if (
|
||||
sig?.dynListener ||
|
||||
flexibleJsEvent(name, sig) ||
|
||||
computedPatterns(lowerer).some(
|
||||
(pattern) => pattern.listener && patternMatchesName(pattern, name),
|
||||
)
|
||||
) {
|
||||
if (listenerArrayIsDyn(lowerer, name, sig)) {
|
||||
return dynListenerArray(lowerer, table, receiver, name, call, loc);
|
||||
}
|
||||
if (sig?.dynListener) {
|
||||
lowerer.noLowering(
|
||||
`events.getEventListeners of the event '${name}'`,
|
||||
call,
|
||||
|
||||
@@ -317,6 +317,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"emitter.emit",
|
||||
"emitter.emitFlex",
|
||||
"emitter.emitAny",
|
||||
"emitter.emitErrorValue",
|
||||
"emitter.emitMixed",
|
||||
"emitter.listenersDyn",
|
||||
"emitter.emitError",
|
||||
|
||||
@@ -4349,6 +4349,9 @@ function buildLibFnSignatures() {
|
||||
"emitter.emit": { argTypes: [null, STRING], result: BOOL },
|
||||
"emitter.emitFlex": { argTypes: [null, STRING], result: BOOL },
|
||||
"emitter.emitAny": { argTypes: [null, STRING], result: BOOL },
|
||||
/** emit('error', ...payload) with a non-Error payload: (recv, then the
|
||||
* arguments boxed) — VARIADIC like emitFlex. */
|
||||
"emitter.emitErrorValue": { argTypes: [null], result: BOOL },
|
||||
/** An untyped emit of an event with a fixed tuple: (recv, name, k, then
|
||||
* k checked-dynamic arguments as given, then the event's converted
|
||||
* tuple) — VARIADIC like emitter.emit. hasTyped answers whether a
|
||||
|
||||
@@ -1121,6 +1121,8 @@ const RUNTIME_FEATURE_RULES = {
|
||||
"webstream.class",
|
||||
"webstream.install",
|
||||
"util.styleText",
|
||||
// ERR_UNHANDLED_ERROR quotes util.inspect of a non-Error payload.
|
||||
"emitter.emitErrorValue",
|
||||
"crypto.cipherNew",
|
||||
"webcrypto.encrypt",
|
||||
"webcrypto.decrypt",
|
||||
|
||||
@@ -4154,6 +4154,7 @@ function validateFunction(
|
||||
e.fn === "emitter.emit" ||
|
||||
e.fn === "emitter.emitFlex" ||
|
||||
e.fn === "emitter.emitAny" ||
|
||||
e.fn === "emitter.emitErrorValue" ||
|
||||
e.fn === "emitter.emitMixed" ||
|
||||
e.fn === "readable.new" ||
|
||||
e.fn === "writable.new" ||
|
||||
@@ -6191,6 +6192,12 @@ function validateFunction(
|
||||
err(`libCall ${e.fn} payloads must be checked-dynamic`, e.loc);
|
||||
}
|
||||
}
|
||||
if (
|
||||
e.fn === "emitter.emitErrorValue" &&
|
||||
e.args.slice(1).some((arg) => arg.type.kind !== "dyn")
|
||||
) {
|
||||
err(`libCall emitter.emitErrorValue payloads must be checked-dynamic`, e.loc);
|
||||
}
|
||||
if (e.fn === "emitter.emitMixed") {
|
||||
const count = e.args[2];
|
||||
const k = count?.kind === "numLit" ? count.value : -1;
|
||||
@@ -6204,6 +6211,7 @@ function validateFunction(
|
||||
e.fn === "emitter.emit" ||
|
||||
e.fn === "emitter.emitFlex" ||
|
||||
e.fn === "emitter.emitAny" ||
|
||||
e.fn === "emitter.emitErrorValue" ||
|
||||
e.fn === "emitter.emitMixed" ||
|
||||
e.fn === "emitter.hasTyped" ||
|
||||
e.fn === "emitter.listenersDyn" ||
|
||||
|
||||
@@ -17,17 +17,6 @@ test.each([
|
||||
const ee = new Derived(); ee.on('value', (value) => console.log(value)); ee.emit('value'); ee.emit('value', 1);`,
|
||||
"variable-arity emit through an EventEmitter subclass overriding emit",
|
||||
],
|
||||
[
|
||||
"listener arrays",
|
||||
`const ee = new EventEmitter(); ee.on('value', () => {});
|
||||
ee.emit('value'); ee.emit('value', 1); console.log(ee.listeners('value'));`,
|
||||
"listeners of the event 'value'",
|
||||
],
|
||||
[
|
||||
"special error events",
|
||||
`const ee = new EventEmitter(); ee.on('error', (value) => console.log(value)); ee.emit('error');`,
|
||||
"emit('error') with 0 payload arguments",
|
||||
],
|
||||
[
|
||||
"reserved stream events",
|
||||
`const ee = new EventEmitter(); ee.on('data', (value) => console.log(value));
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/emitter-computed-name-listeners.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/emitter-listeners-dynamic-typed.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/emitter-listeners-dynamic.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/emitter-meta-listeners-dynamic.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/emitter-unhandled-error-payloads-typed.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/emitter-unhandled-error-payloads.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/ee-computed-listeners.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -1193,13 +1193,29 @@ bool scr_emitter_has_typed(ScrEmitter *em, ScrStr *name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Node's ERR_UNHANDLED_ERROR for a non-Error payload: the message quotes
|
||||
* util.inspect of the payload. This unit renders the primitive payloads
|
||||
* inspect prints plainly; others report that they are not supported. */
|
||||
static void scr_ee_unhandled_error(const ScrDyn *er) {
|
||||
/* Throws Node's ERR_UNHANDLED_ERROR for a non-Error payload: the message
|
||||
* quotes `shown` (util.inspect of the payload) and `context` is the
|
||||
* payload. */
|
||||
SCR_COLD static void scr_ee_throw_unhandled(const char *shown, size_t len, const ScrDyn *er) {
|
||||
ScrJsonBuf text;
|
||||
scr_jb_init(&text);
|
||||
scr_jb_puts(&text, "Unhandled error. (");
|
||||
scr_jb_write(&text, shown, len);
|
||||
scr_jb_putc(&text, ')');
|
||||
ScrStr *message = scr_jb_finish(&text);
|
||||
ScrError *err = scr_error_new(SCR_ERR_ERROR, message);
|
||||
scr_str_release(message);
|
||||
scr_error_set_code(err, "ERR_UNHANDLED_ERROR");
|
||||
scr_error_set_prop(err, "context", 7, scr_dyn_retain((ScrDyn *)er));
|
||||
scr_throw_obj(err, &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg());
|
||||
}
|
||||
|
||||
/* ERR_UNHANDLED_ERROR through an untyped value. This unit renders the
|
||||
* primitive payloads inspect prints plainly; others report that they are
|
||||
* not supported. */
|
||||
static void scr_ee_unhandled_error(const ScrDyn *er) {
|
||||
ScrJsonBuf text;
|
||||
scr_jb_init(&text);
|
||||
bool shown = true;
|
||||
switch (er->kind) {
|
||||
case SCR_DYN_UNDEF: scr_jb_puts(&text, "undefined"); break;
|
||||
@@ -1233,13 +1249,7 @@ static void scr_ee_unhandled_error(const ScrDyn *er) {
|
||||
}
|
||||
ScrStr *message = scr_jb_finish(&text);
|
||||
if (shown) {
|
||||
ScrJsonBuf full;
|
||||
scr_jb_init(&full);
|
||||
scr_jb_write(&full, message->data, message->len);
|
||||
scr_jb_putc(&full, ')');
|
||||
ScrStr *rendered = scr_jb_finish(&full);
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, rendered->data, rendered->len, "ERR_UNHANDLED_ERROR");
|
||||
scr_str_release(rendered);
|
||||
scr_ee_throw_unhandled(message->data, message->len, er);
|
||||
} else {
|
||||
const char *refusal =
|
||||
"emit('error') through an untyped value with this non-Error payload and no 'error' listener is not supported yet";
|
||||
@@ -1251,16 +1261,35 @@ static void scr_ee_unhandled_error(const ScrDyn *er) {
|
||||
/* emit('error', ...) through an untyped value: errorMonitor first; with no
|
||||
* 'error' listener an Error payload throws itself and anything else throws
|
||||
* ERR_UNHANDLED_ERROR, Node's contract; otherwise listeners receive the
|
||||
* arguments as given, and typed listeners the payload as an Error. */
|
||||
static bool scr_ee_emit_error_dyn(ScrEmitter *em, ScrStr *name, ScrDyn *const *args, size_t argc) {
|
||||
* arguments as given, and typed listeners the payload as an Error. `render`
|
||||
* (util.inspect, when the call site links it) quotes the payload in the
|
||||
* ERR_UNHANDLED_ERROR message; without it only primitive payloads render. */
|
||||
static bool scr_ee_emit_error_dyn(ScrEmitter *em, ScrStr *name, ScrDyn *const *args, size_t argc,
|
||||
ScrStr *(*render)(ScrDyn *, double, double)) {
|
||||
const ScrDyn *er = argc ? args[0] : scr_dyn_undefined();
|
||||
ScrError *err = scr_dyn_err_instanceof(er, SCR_ERR_ERROR) ? scr_errdyn_err_of(er) : NULL;
|
||||
bool had = false;
|
||||
if (!scr_ee_has(em->reg, "error")) {
|
||||
scr_ee_emit_mixed_va(em, SCR_EE_ERROR_MONITOR, args, argc, err != NULL, err);
|
||||
if (!scr_exc_pending()) {
|
||||
if (err) scr_dyn_throw(scr_dyn_retain((ScrDyn *)er));
|
||||
else scr_ee_unhandled_error(er);
|
||||
if (err) {
|
||||
scr_dyn_throw(scr_dyn_retain((ScrDyn *)er));
|
||||
} else if (render) {
|
||||
ScrStr *shown = render((ScrDyn *)er, 0, 2);
|
||||
if (scr_exc_pending()) {
|
||||
/* A throwing inspect: Node quotes the payload's string conversion
|
||||
* instead, whose own throw propagates. */
|
||||
if (shown) scr_str_release(shown);
|
||||
scr_exc_clear();
|
||||
shown = scr_dyn_string_coerce_js(er);
|
||||
}
|
||||
if (shown) {
|
||||
scr_ee_throw_unhandled(shown->data, shown->len, er);
|
||||
scr_str_release(shown);
|
||||
}
|
||||
} else {
|
||||
scr_ee_unhandled_error(er);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
had = scr_ee_emit_mixed_va(em, name, args, argc, err != NULL, err);
|
||||
@@ -1269,6 +1298,17 @@ static bool scr_ee_emit_error_dyn(ScrEmitter *em, ScrStr *name, ScrDyn *const *a
|
||||
return had;
|
||||
}
|
||||
|
||||
/* emit('error', ...args) from typed code whose payload is not statically
|
||||
* an Error (the arguments boxed): scr_ee_emit_error_dyn, quoting the
|
||||
* payload with `render`. */
|
||||
bool scr_emitter_emit_error_value(ScrEmitter *em, ScrDyn *const *args, size_t argc,
|
||||
ScrStr *(*render)(ScrDyn *, double, double)) {
|
||||
ScrStr *name = scr_str_new("error", 5);
|
||||
bool had = scr_ee_emit_error_dyn(em, name, args, argc, render);
|
||||
scr_str_release(name);
|
||||
return had;
|
||||
}
|
||||
|
||||
/* An untyped emit of a name typed code never fixes a tuple for (computed
|
||||
* names, runtime-internal registries): the arguments as given. */
|
||||
bool scr_emitter_emit_flex(ScrEmitter *em, ScrStr *name, ScrDyn *const *args, size_t argc) {
|
||||
@@ -1288,7 +1328,7 @@ bool scr_emitter_emit_message(ScrEmitter *em, ScrStr *name, const struct ScrMess
|
||||
* typed listeners with a string name. */
|
||||
bool scr_emitter_emit_any(ScrEmitter *em, ScrStr *name, ScrDyn *const *args, size_t argc) {
|
||||
if (!scr_ee_is_error_monitor(name)) {
|
||||
if (scr_ee_is_name(name, "error")) return scr_ee_emit_error_dyn(em, name, args, argc);
|
||||
if (scr_ee_is_name(name, "error")) return scr_ee_emit_error_dyn(em, name, args, argc, NULL);
|
||||
if (scr_ee_is_name(name, "newListener") || scr_ee_is_name(name, "removeListener")) {
|
||||
ScrStr *s = argc && args[0]->kind == SCR_DYN_STR ? args[0]->v.str : NULL;
|
||||
return scr_ee_emit_mixed_va(em, name, args, argc, s != NULL, s);
|
||||
|
||||
@@ -109,6 +109,10 @@ ScrEmitter *scr_emitter_on_flex(ScrEmitter *em, ScrStr *name, const ScrDyn *cb,
|
||||
ScrEmitter *scr_emitter_on_any(ScrEmitter *em, ScrStr *name, const ScrDyn *cb,
|
||||
bool once, bool prepend);
|
||||
bool scr_emitter_emit_any(ScrEmitter *em, ScrStr *name, ScrDyn *const *args, size_t argc);
|
||||
/* emit('error', ...args) with a non-Error payload from typed code, the
|
||||
* arguments boxed; `render` is util.inspect's dyn entry (scr_insp_dyn). */
|
||||
bool scr_emitter_emit_error_value(ScrEmitter *em, ScrDyn *const *args, size_t argc,
|
||||
ScrStr *(*render)(ScrDyn *, double, double));
|
||||
ScrEmitter *scr_emitter_off_dyn(ScrEmitter *em, ScrStr *name, const ScrDyn *cb);
|
||||
/* The registry's checked-dynamic half (meta-event argument vectors and
|
||||
* rebuilt listener values), installed by the first on_any call; NULL in
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// listeners() and rawListeners() from TypeScript of an event registered
|
||||
// under a computed name: the registered functions in order.
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
const emitter = new EventEmitter();
|
||||
const suffix = process.argv[2] ?? "x";
|
||||
const onTopic = (): void => console.log("topic listener");
|
||||
emitter.on(`topic:${suffix}`, onTopic);
|
||||
emitter.on(`topic:${suffix}`, () => console.log("second"));
|
||||
console.log(emitter.listeners("topic:x").length, emitter.listeners("topic:x")[0] === onTopic);
|
||||
for (const l of emitter.listeners("topic:x")) l();
|
||||
console.log(emitter.listeners("topic:y").length);
|
||||
emitter.off("topic:x", onTopic);
|
||||
console.log(emitter.rawListeners("topic:x").length);
|
||||
@@ -0,0 +1,15 @@
|
||||
// listeners() and rawListeners() from TypeScript of an event with a typed
|
||||
// listener and a checked-dynamic one: each element is the registered
|
||||
// function, called with the event's arguments.
|
||||
import { EventEmitter } from "node:events";
|
||||
const e = new EventEmitter();
|
||||
const typed = (n: number, s: string): void => console.log("typed", n, s);
|
||||
const loose = ((...args: unknown[]) => console.log("loose", args.length)) as any;
|
||||
e.on("tick", typed);
|
||||
e.on("tick", loose);
|
||||
e.emit("tick", 1, "one");
|
||||
console.log(e.listeners("tick").length, e.listeners("tick")[0] === typed, e.rawListeners("tick")[1] === loose);
|
||||
for (const l of e.listeners("tick")) (l as (n: number, s: string) => void)(2, "two");
|
||||
e.off("tick", typed);
|
||||
e.emit("tick", 3, "three");
|
||||
console.log(e.listenerCount("tick"), e.listeners("tick").length);
|
||||
@@ -0,0 +1,32 @@
|
||||
// listeners(), rawListeners() and getEventListeners() of events whose
|
||||
// listeners include functions with untyped parameters or that read
|
||||
// arguments: the array holds the registered functions in order.
|
||||
const { EventEmitter, getEventListeners } = require('events');
|
||||
const e = new EventEmitter();
|
||||
function typed() { console.log('typed'); }
|
||||
function packed() { console.log('packed', arguments.length); }
|
||||
function withParam(a) { console.log('withParam', a); }
|
||||
e.on('foo', typed);
|
||||
e.on('foo', packed);
|
||||
e.once('foo', withParam);
|
||||
let fl = e.listeners('foo');
|
||||
console.log(fl.length, fl[0] === typed, fl[1] === packed, fl[2] === withParam);
|
||||
const raw = e.rawListeners('foo');
|
||||
console.log(raw.length, raw[2] === withParam || typeof raw[2].listener === 'function');
|
||||
console.log(getEventListeners(e, 'foo').length);
|
||||
e.emit('foo', 1, 2);
|
||||
fl = e.listeners('foo');
|
||||
console.log(fl.length);
|
||||
fl[0]();
|
||||
fl[1](7, 8, 9);
|
||||
e.off('foo', typed);
|
||||
console.log(e.listeners('foo').length, e.listeners('foo')[0] === packed);
|
||||
e.removeListener('foo', packed);
|
||||
console.log(e.listeners('foo').length, e.listenerCount('foo'));
|
||||
const g = new EventEmitter();
|
||||
g.on('bar', function (x) { console.log('bar', x); });
|
||||
g.on('bar', (x, y) => console.log('bar2', x, y));
|
||||
g.emit('bar', 'a');
|
||||
g.emit('bar', 'a', 'b', 'c');
|
||||
console.log(g.listeners('bar').length, typeof g.listeners('bar')[1]);
|
||||
console.log(e.listeners('nothing').length);
|
||||
@@ -0,0 +1,37 @@
|
||||
// Checked-dynamic 'newListener' and 'removeListener' listeners receive the
|
||||
// event name and the added or removed listener function.
|
||||
const EventEmitter = require('events');
|
||||
function listener1() {}
|
||||
function listener2(a) { console.log('l2', a); }
|
||||
const ee = new EventEmitter();
|
||||
ee.on('newListener', function (name, fn) {
|
||||
console.log('new', name, fn === listener1, fn === listener2, typeof fn);
|
||||
});
|
||||
ee.on('hello', listener1);
|
||||
ee.on('hello', listener2);
|
||||
ee.on('removeListener', (name, cb) => {
|
||||
console.log('removed', name, cb === listener1, cb === listener2);
|
||||
});
|
||||
ee.removeListener('hello', listener1);
|
||||
ee.emit('hello', 5);
|
||||
console.log(ee.listeners('hello').length, ee.listeners('hello')[0] === listener2);
|
||||
ee.removeAllListeners('hello');
|
||||
console.log(ee.listenerCount('hello'));
|
||||
const other = new EventEmitter();
|
||||
other.once('removeListener', (name, cb) => console.log('once-removed', name, typeof cb));
|
||||
other.on('x', (n) => console.log('x', n));
|
||||
other.emit('x', 1);
|
||||
other.removeAllListeners();
|
||||
console.log(other.eventNames());
|
||||
|
||||
// A meta listener produced by a wrapper factory, as test helpers do.
|
||||
function wrap(fn) {
|
||||
return function () {
|
||||
return fn.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
function plain() {}
|
||||
const wrapped = new EventEmitter();
|
||||
wrapped.on('ping', plain);
|
||||
wrapped.on('removeListener', wrap((name, cb) => console.log('wrapped removed', name, cb === plain)));
|
||||
wrapped.removeListener('ping', plain);
|
||||
@@ -0,0 +1,46 @@
|
||||
// emit('error', payload) from typed code with a payload that is not an
|
||||
// Error: Node's ERR_UNHANDLED_ERROR when nothing listens, the payload as
|
||||
// given otherwise.
|
||||
import { EventEmitter, errorMonitor } from "node:events";
|
||||
|
||||
interface UnhandledError extends Error {
|
||||
code?: string;
|
||||
context?: unknown;
|
||||
}
|
||||
|
||||
const e = new EventEmitter();
|
||||
const record = { id: 3, tags: ["a", "b"] };
|
||||
const attempts: Array<() => boolean> = [
|
||||
() => e.emit("error", "plain string"),
|
||||
() => e.emit("error", 12.5),
|
||||
() => e.emit("error", record),
|
||||
() => e.emit("error", [1, 2, 3]),
|
||||
() => e.emit("error", null),
|
||||
() => e.emit("error"),
|
||||
() => e.emit("error", "a", "b"),
|
||||
];
|
||||
for (const attempt of attempts) {
|
||||
try {
|
||||
attempt();
|
||||
console.log("no throw");
|
||||
} catch (caught) {
|
||||
const err = caught as UnhandledError;
|
||||
console.log(err.code, err.message, err.context === record, typeof err.context);
|
||||
}
|
||||
}
|
||||
|
||||
let monitored = 0;
|
||||
e.on(errorMonitor, () => {
|
||||
monitored++;
|
||||
});
|
||||
try {
|
||||
e.emit("error", "watched");
|
||||
} catch (caught) {
|
||||
console.log((caught as UnhandledError).code, monitored);
|
||||
}
|
||||
|
||||
const handled = new EventEmitter();
|
||||
handled.on("error", (err: unknown) => console.log("got", err instanceof Error ? `Error: ${err.message}` : err));
|
||||
console.log(handled.emit("error", 7));
|
||||
console.log(handled.emit("error", { reason: "closed" }));
|
||||
console.log(handled.emit("error", new Error("real")));
|
||||
@@ -0,0 +1,67 @@
|
||||
// emit('error', payload) with a payload that is not an Error: with no
|
||||
// 'error' listener it throws ERR_UNHANDLED_ERROR, whose message quotes
|
||||
// util.inspect of the payload and whose context is the payload; listeners
|
||||
// receive the arguments as given.
|
||||
const { EventEmitter, errorMonitor } = require("events");
|
||||
|
||||
const e = new EventEmitter();
|
||||
const payloads = ["Accepts a string", "it's", { message: "Error!" }, 42, -0, null, undefined, true, [1, "two"], { nested: { deep: [1, 2] } }];
|
||||
for (const v of payloads) {
|
||||
try {
|
||||
e.emit("error", v);
|
||||
console.log("no throw");
|
||||
} catch (err) {
|
||||
console.log(err instanceof Error, err.name, err.code, err.message, err.context === v);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
e.emit("error");
|
||||
} catch (err) {
|
||||
console.log(err.code, err.message, err.context);
|
||||
}
|
||||
|
||||
try {
|
||||
e.emit("error", "first", "second");
|
||||
} catch (err) {
|
||||
console.log(err.code, err.message, err.context);
|
||||
}
|
||||
|
||||
// errorMonitor sees the payload before the throw.
|
||||
const m = new EventEmitter();
|
||||
m.on(errorMonitor, function (v) {
|
||||
console.log("monitor", v, arguments.length);
|
||||
});
|
||||
try {
|
||||
m.emit("error", { code: 7 });
|
||||
} catch (err) {
|
||||
console.log("thrown", err.code, err.context.code);
|
||||
}
|
||||
|
||||
// With an 'error' listener nothing throws.
|
||||
const l = new EventEmitter();
|
||||
l.on("error", function (a, b) {
|
||||
console.log("listener", a, b, arguments.length);
|
||||
});
|
||||
console.log(l.emit("error", "x", 2));
|
||||
console.log(l.emit("error", { k: 1 }));
|
||||
console.log(l.emit("error"));
|
||||
|
||||
// An Error payload still throws itself.
|
||||
try {
|
||||
e.emit("error", new TypeError("typed"));
|
||||
} catch (err) {
|
||||
console.log(err.name, err.message, err.code);
|
||||
}
|
||||
|
||||
// A payload whose util.inspect throws is quoted by its string conversion.
|
||||
try {
|
||||
e.emit("error", {
|
||||
message: "Error!",
|
||||
[require("util").inspect.custom]() {
|
||||
throw new Error("no inspect");
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err.code, err.message);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
const emitter = new EventEmitter();
|
||||
const suffix = process.argv[2] ?? "x";
|
||||
emitter.on(`topic:${suffix}`, () => {});
|
||||
emitter.listeners("topic:x");
|
||||
@@ -1,8 +0,0 @@
|
||||
ee-computed-listeners.ts:6:1 - error SC2020: 'listeners of the event 'topic:x'' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
5 | emitter.on(`topic:${suffix}`, () => {});
|
||||
6 | emitter.listeners("topic:x");
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
7 |
|
||||
|
||||
hint: a listener of this event uses checked-dynamic dispatch, so the listener array has no one static element type — listenerCount(name) counts
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4200, "san": 7100 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4700, "san": 7300 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 6300, "san": 8700 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4800, "san": 7700 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4900, "san": 8500 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5300, "san": 8800 }
|
||||
Reference in New Issue
Block a user