diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 2d91386b3..d01374235 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -994,6 +994,7 @@ export const LIB_FN_SYMS: Record = { // family have non-generic shapes (adapter/variadic) — special-cased in // emitLibCall. "emitter.new": "scr_emitter_new", + "emitter.className": "scr_emitter_class_name", "emitter.ctor": "scr_emitter_init", "emitter.off": "scr_emitter_off", "emitter.offDyn": "scr_emitter_off_dyn", diff --git a/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts b/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts new file mode 100644 index 000000000..4b61f98c1 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts @@ -0,0 +1,181 @@ +import * as ts from "../../ts7/adapter.js"; +import { type Lowerer } from "../lowerer.js"; +import { type ClassInfo } from "../lower-classes.js"; +import { isJsSourceFile, locOf } from "../../program.js"; +import { varRef } from "../../../ir/build.js"; +import { BOOL, STRING, type IrExpr, type IrType, type SrcLoc } from "../../../ir/ir.js"; + +/** `x.constructor` on an instance typed as EventEmitter or a stream class + * in a TypeScript file: the receiver's runtime ClassInfo, or null. The + * runtime records each instance's Node class name (EventEmitter, Readable, + * ReadStream, Gzip, Socket, a program subclass's name), which the + * lowerings below read with emitter.className. */ +export function runtimeConstructorReceiver( + lowerer: Lowerer, + expr: ts.Expression, +): { receiver: ts.Expression; info: ClassInfo } | null { + let e = expr; + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (!ts.isPropertyAccessExpression(e) || e.questionDotToken) return null; + if (e.name.text !== "constructor" || isJsSourceFile(e.getSourceFile())) return null; + const mapped = lowerer.mapTypeOf(lowerer.typeOf(e.expression)); + if (mapped?.kind !== "object") return null; + const info = lowerer.classes.get(mapped.className); + if (!info?.def.runtime || (!info.builtinEmitter && info.builtinStream === undefined)) return null; + return { receiver: e.expression, info }; +} + +/** `x.constructor.name`: the instance's class name. */ +export function lowerRuntimeConstructorName( + lowerer: Lowerer, + receiver: ts.Expression, + loc: SrcLoc, +): IrExpr { + return { + kind: "libCall", + fn: "emitter.className", + args: [lowerer.lowerExpr(receiver)], + type: STRING, + loc, + }; +} + +/** `x.constructor === C` for EventEmitter or a stream class C: true when + * the instance's own class is C, which needs both the vtable (C's own + * interval, not a subclass's) and the class name (fs, zlib, crypto, child + * process and worker streams share the base classes' vtables). */ +export function lowerRuntimeConstructorIs( + lowerer: Lowerer, + receiver: ts.Expression, + target: ClassInfo, + negated: boolean, + loc: SrcLoc, +): IrExpr { + const value = lowerer.lowerExpr(receiver); + const local = lowerer.declareHiddenLocal("%constructorOf", value.type); + const ref = varRef(local.id, value.type, loc); + const test = exactClassTest(lowerer, ref, target, loc); + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }], + result: negated ? { kind: "unary", op: "!", operand: test, type: BOOL, loc } : test, + type: BOOL, + loc, + }; +} + +/** `x.constructor` as a value: the class object of the instance's own class + * among EventEmitter and the stream classes, as the receiver's static + * class's class value. Instances of the other runtime classes (fs, zlib, + * crypto, child process and worker streams) and of program subclasses + * report SC1090 at run time. */ +export function lowerRuntimeConstructor( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, + info: ClassInfo, +): IrExpr { + const loc = locOf(access); + const resultT: IrType = { kind: "classval", className: info.def.name }; + const value = lowerer.lowerExpr(access.expression); + const local = lowerer.declareHiddenLocal("%constructorOf", value.type); + const ref = varRef(local.id, value.type, loc); + let result: IrExpr = refusalCall(lowerer, resultT, loc); + for (const candidate of valueClassesUnder(info)) { + lowerer.defineClassValueConstruct(candidate); + lowerer.defineClassValueConstruct(info); + const classRef: IrExpr = { + kind: "classRef", + className: candidate.def.name, + type: { kind: "classval", className: candidate.def.name }, + loc, + }; + result = { + kind: "ternary", + cond: exactClassTest(lowerer, ref, candidate, loc), + then: candidate === info ? classRef : { kind: "upcast", value: classRef, type: resultT, loc }, + else_: result, + type: resultT, + loc, + }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }], + result, + type: resultT, + loc, + }; +} + +/** `info` and its runtime descendants whose values construct. */ +function valueClassesUnder(info: ClassInfo): ClassInfo[] { + const out: ClassInfo[] = []; + const visit = (c: ClassInfo): void => { + if (!c.def.runtime) return; + if (c.valueCtorParams !== undefined) out.push(c); + for (const s of c.subclasses) visit(s); + }; + visit(info); + return out; +} + +/** The instance is of class `target` itself: inside its interval, outside + * each direct subclass's (runtime or program), and named as it. */ +function exactClassTest(lowerer: Lowerer, ref: IrExpr, target: ClassInfo, loc: SrcLoc): IrExpr { + let test: IrExpr = { + kind: "instanceOf", + value: ref, + className: target.def.name, + type: BOOL, + loc, + }; + for (const sub of target.subclasses) { + test = { + kind: "logical", + op: "&&", + left: test, + right: { + kind: "unary", + op: "!", + operand: { kind: "instanceOf", value: ref, className: sub.def.name, type: BOOL, loc }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }; + } + const name: IrExpr = { + kind: "strEq", + negated: false, + left: { kind: "libCall", fn: "emitter.className", args: [ref], type: STRING, loc }, + right: { kind: "strLit", value: target.def.name.slice(1), type: STRING, loc }, + type: BOOL, + loc, + }; + return { kind: "logical", op: "&&", left: test, right: name, type: BOOL, loc }; +} + +/** The run-time refusal for an instance whose class has no class value. */ +function refusalCall(lowerer: Lowerer, type: IrType, loc: SrcLoc): IrExpr { + const name = `%constructor.refusal:${type.kind === "classval" ? type.className : ""}`; + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + lowerer.liftedFns.push({ + name, + params: [], + locals: [], + returnType: type, + body: [ + { + kind: "runtimeFence", + code: "SC1090", + message: + "reading 'constructor' of an fs, zlib, crypto, child process or worker stream, or of an instance of a program subclass of EventEmitter or a stream class, is not supported yet", + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: name, args: [], type, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index 2a8de78b9..cecbde717 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -66,6 +66,10 @@ import { lowerAbstractEquality } from "../abstract-equality.js"; import { lowerInExpression } from "./relational.js"; import { lowerDynTypeofTest, lowerErrorCodeTypeofTest } from "./typeof.js"; import { builtinClassRefOf, sameBuiltinClass } from "../builtins/class-values.js"; +import { + lowerRuntimeConstructorIs, + runtimeConstructorReceiver, +} from "../builtins/runtime-constructor.js"; import { tryLowerIndexedComparison } from "../indexed-comparison.js"; import { lowerUnionEquality, tagEqualityMayMissAlias } from "../strict-equality.js"; import { withUnitArm } from "../../type-mapper.js"; @@ -1322,6 +1326,24 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr lowerUnionTypeofTest(lowerer, expr, loc); if (test) return test; } + // `x.constructor === Readable` for an instance of EventEmitter or a + // stream class: the instance's own class, compared without a class value. + if ( + op === ts.SyntaxKind.EqualsEqualsEqualsToken || + op === ts.SyntaxKind.ExclamationEqualsEqualsToken + ) { + for (const [side, other] of [ + [expr.left, expr.right], + [expr.right, expr.left], + ] as const) { + const ctor = runtimeConstructorReceiver(lowerer, side); + const target = ctor ? builtinClassRefOf(lowerer, other) : null; + if (ctor && (target?.kind === "emitter" || target?.kind === "stream")) { + const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken; + return lowerRuntimeConstructorIs(lowerer, ctor.receiver, target.info, negated, loc); + } + } + } // `C === EventEmitter` between two builtin class references: the // classes are compile-time values, so the answer folds. if ( diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 7f0b2ddbb..1a5298379 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -76,6 +76,11 @@ import { lifetimeRegionMemberOf } from "../../experimental-import.js"; import { lowerInternTableProperty } from "../intern-tables.js"; import { lowerThreadSlotsProperty } from "../thread-slots.js"; import { builtinClassRefOf, runtimeClassValueOfType } from "../builtins/class-values.js"; +import { + lowerRuntimeConstructor, + lowerRuntimeConstructorName, + runtimeConstructorReceiver, +} from "../builtins/runtime-constructor.js"; import { findGenericMethodOn, lowerStaticFieldRead } from "../lower-classes.js"; import { lowerClassPrototypeData } from "../class-prototypes.js"; import { lowerEnumAccess } from "../lower-enums.js"; @@ -290,6 +295,16 @@ export function lowerPropertyAccessExpr( const ref = builtinClassRefOf(lowerer, expr.expression); if (ref !== null) return { kind: "strLit", value: ref.name, type: STRING, loc }; } + // `x.constructor.name` and `x.constructor` on an instance of + // EventEmitter or a stream class (builtins/runtime-constructor.ts). + if (expr.name.text === "name" && !expr.questionDotToken) { + const ctor = runtimeConstructorReceiver(lowerer, expr.expression); + if (ctor) return lowerRuntimeConstructorName(lowerer, ctor.receiver, loc); + } + { + const ctor = runtimeConstructorReceiver(lowerer, expr); + if (ctor) return lowerRuntimeConstructor(lowerer, expr, ctor.info); + } // `C.name` through a runtime-provided class VALUE (a `typeof Readable` // parameter): the class object's stored name. if ( diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 697a185a5..2a0e5124a 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3499,6 +3499,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * throws err when unhandled. count/countFn/names/listeners/getMax/ * setMax/setDefaultMax/getDefaultMax are the introspection surface. */ "emitter.new": { argTypes: [], result: VOID }, + /** `x.constructor.name` of an emitter or stream (a borrowed object of + * EventEmitter's hierarchy): the +1 class name the instance records. */ + "emitter.className": { argTypes: [null], result: STRING }, "emitter.ctor": { argTypes: [null], result: VOID }, "emitter.on": { argTypes: [null, STRING, null, BOOL, BOOL], result: VOID }, "emitter.off": { argTypes: [null, STRING, null], result: VOID }, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5983-runtime-constructor.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5983-runtime-constructor.ts.json new file mode 100644 index 000000000..004b1e350 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5983-runtime-constructor.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5983-runtime-constructor.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_child.c b/packages/runtime/src/scr_child.c index 87ead5755..c714fb555 100644 --- a/packages/runtime/src/scr_child.c +++ b/packages/runtime/src/scr_child.c @@ -6458,7 +6458,7 @@ static ScrDyn *scr_child_bridge_readable(ScrChildStream *reader, ScrDyn **cached return NULL; } if (!*cached) { - ScrStream *stream = scr_child_stream_ops->readable(); + ScrStream *stream = scr_child_stream_ops->readable("Socket"); scr_child_stream_on_data(reader, scr_child_bridge_callback(stream, (void *)scr_child_bridge_data), scr_child_bridge_data, false); scr_child_stream_on_end(reader, scr_child_bridge_callback(stream, (void *)scr_child_bridge_end), false); @@ -6578,7 +6578,7 @@ static ScrDyn *scr_child_bridge_writable(ScrChildWriter *writer, ScrDyn **cached return NULL; } if (!*cached) { - ScrStream *stream = scr_child_stream_ops->writable( + ScrStream *stream = scr_child_stream_ops->writable("Socket", scr_child_writer_bridge_callback(writer, (void *)scr_child_bridge_write), scr_child_bridge_write, scr_child_writer_bridge_callback(writer, (void *)scr_child_bridge_final), scr_child_bridge_final); *cached = scr_child_writable_box(stream); diff --git a/packages/runtime/src/scr_child.h b/packages/runtime/src/scr_child.h index 1187985c8..bbb50e0cb 100644 --- a/packages/runtime/src/scr_child.h +++ b/packages/runtime/src/scr_child.h @@ -253,9 +253,13 @@ void scr_child_dyn_install(void); void scr_child_on_dyn(ScrChild *child, ScrStr *event, ScrDyn *listener); /* borrowed */ /* Optional native-stream bridge. The table avoids a stream dependency * in programs that only use the child-process surface. */ +/* readable/writable take the stream's class name (the constructor name + * Node reports: Socket for child stdio, ReadableWorkerStdio and + * WritableWorkerStdio for worker stdio). */ typedef struct ScrChildStreamOps { - ScrStream *(*readable)(void); - ScrStream *(*writable)(ScrClosure *, ScrStreamChunkInv, ScrClosure *, ScrStreamPlainInv); + ScrStream *(*readable)(const char *cls); + ScrStream *(*writable)(const char *cls, ScrClosure *, ScrStreamChunkInv, ScrClosure *, + ScrStreamPlainInv); void *(*retain)(void *); void (*release)(void *); ScrTraceFn trace; diff --git a/packages/runtime/src/scr_crypto.h b/packages/runtime/src/scr_crypto.h index 4b6b6a6a6..d71f46b13 100644 --- a/packages/runtime/src/scr_crypto.h +++ b/packages/runtime/src/scr_crypto.h @@ -71,6 +71,7 @@ void scr_crypto_hash_stream_set(ScrCryptoHash *hash, void *stream, void (*releas void scr_crypto_hash_stream_detach(void *hash); void scr_crypto_hash_stream_close(ScrCryptoHash *hash); bool scr_crypto_hash_stream_usable(const ScrCryptoHash *hash); +bool scr_crypto_hash_is_hmac(const ScrCryptoHash *hash); void scr_crypto_hash_stream_hold(ScrCryptoHash *hash); void scr_crypto_hash_stream_unhold(ScrCryptoHash *hash); ScrStream *scr_crypto_hash_stream_piped(ScrCryptoHash *hash); diff --git a/packages/runtime/src/scr_events_emitter.c b/packages/runtime/src/scr_events_emitter.c index ceecb15e3..e0e305bde 100644 --- a/packages/runtime/src/scr_events_emitter.c +++ b/packages/runtime/src/scr_events_emitter.c @@ -324,6 +324,13 @@ ScrEmitter *scr_emitter_new(void) { return em; } +/* x.constructor.name of an emitter, a stream, or an instance of an emitted + * subclass: the class name every allocation stamps (EventEmitter, + * Readable, ReadStream, Gzip, Socket, the program class's name). +1. */ +ScrStr *scr_emitter_class_name(const ScrEmitter *em) { + return scr_str_new(em->cls, strlen(em->cls)); +} + /* super() into the emitter prefix of an emitted subclass: the allocation * already zeroed reg and stamped the concrete class's display name, so * construction has nothing left to do — the call exists so the super() diff --git a/packages/runtime/src/scr_events_emitter.h b/packages/runtime/src/scr_events_emitter.h index 99da57a50..a92ae1864 100644 --- a/packages/runtime/src/scr_events_emitter.h +++ b/packages/runtime/src/scr_events_emitter.h @@ -38,6 +38,7 @@ extern SCR_TL ScrVt scr_emitter_vt; /* main() stamps pre/post */ extern SCR_TL double scr_emitter_default_max; ScrEmitter *scr_emitter_new(void); /* +1, collector-headered */ +ScrStr *scr_emitter_class_name(const ScrEmitter *em); /* +1 */ /* Internal owner hook; observes listener presence without running script. */ void scr_emitter_observe(ScrEmitter *em, void (*observe)(void *, const ScrStr *, bool), void *context); diff --git a/packages/runtime/src/scr_fs_stream.c b/packages/runtime/src/scr_fs_stream.c index 97bf4ebaa..70a5ab075 100644 --- a/packages/runtime/src/scr_fs_stream.c +++ b/packages/runtime/src/scr_fs_stream.c @@ -562,6 +562,7 @@ static ScrStream *scr_fss_new(const ScrDyn *path, const ScrDyn *opts_in, bool re scr_dyn_release(eo); } if (!s) goto fail; + s->cls = readable ? "ReadStream" : "WriteStream"; bool unknown_encoding = false; if (!readable) { /* setDefaultEncoding(options.encoding) after the Writable constructor: diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index 930d44818..52036e02c 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -8473,6 +8473,8 @@ void scr_crypto_hash_stream_close(ScrCryptoHash *hash) { if (stream) stream_release(stream); } +bool scr_crypto_hash_is_hmac(const ScrCryptoHash *hash) { return hash->hmac; } + bool scr_crypto_hash_stream_usable(const ScrCryptoHash *hash) { if (!hash->stream_closed) return true; static const char msg[] = "Stream use of a Hash or Hmac after its stream closed has no lowering"; diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 244e2ec5d..c2d27e7ce 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -6350,15 +6350,19 @@ void scr_stream_install(void) { static void scr_stream_child_read(ScrClosure *cb, ScrStream *s, double size) { (void)cb; (void)s; (void)size; } -static ScrStream *scr_stream_child_readable(void) { - return scr_stream_new_readable(-1, true, true, +static ScrStream *scr_stream_child_readable(const char *cls) { + ScrStream *s = scr_stream_new_readable(-1, true, true, scr_closure_new((void *)scr_stream_child_read, 0), scr_stream_child_read, NULL, NULL); + s->cls = cls; + return s; } -static ScrStream *scr_stream_child_writable(ScrClosure *write, ScrStreamChunkInv write_inv, - ScrClosure *final_cb, ScrStreamPlainInv final_inv) { - return scr_stream_new_writable(-1, true, true, write, write_inv, +static ScrStream *scr_stream_child_writable(const char *cls, ScrClosure *write, + ScrStreamChunkInv write_inv, ScrClosure *final_cb, ScrStreamPlainInv final_inv) { + ScrStream *s = scr_stream_new_writable(-1, true, true, write, write_inv, final_cb, final_inv, NULL, NULL); + s->cls = cls; + return s; } const ScrChildStreamOps scr_stream_child_ops = { scr_stream_child_readable, scr_stream_child_writable, @@ -6578,6 +6582,7 @@ ScrStream *scr_crypto_hash_stream(ScrCryptoHash *hash) { scr_hash_stream_step(box), scr_hash_stream_transform, scr_hash_stream_step(box), scr_hash_stream_flush, scr_hash_stream_step(box), scr_hash_stream_destroy); + s->cls = scr_crypto_hash_is_hmac(hash) ? "Hmac" : "Hash"; /* The steps keep the box alive as long as the stream. */ scr_crypto_hash_stream_set(hash, scr_stream_retain(s), scr_stream_release_v, box); scr_box_release(box); diff --git a/packages/runtime/src/scr_worker_events.c b/packages/runtime/src/scr_worker_events.c index 53041f3d5..b4efa3e0d 100644 --- a/packages/runtime/src/scr_worker_events.c +++ b/packages/runtime/src/scr_worker_events.c @@ -122,7 +122,7 @@ static ScrClosure *scr_worker_stdin_callback(ScrWorkerThread *thread, void *fire } static ScrStream *scr_worker_stdin_new(ScrWorkerThread *thread) { - return scr_worker_stdio_ops->writable( + return scr_worker_stdio_ops->writable("WritableWorkerStdio", scr_worker_stdin_callback(thread, (void *)scr_worker_stdin_write), scr_worker_stdin_write, scr_worker_stdin_callback(thread, (void *)scr_worker_stdin_final), scr_worker_stdin_final); } @@ -1350,7 +1350,8 @@ static ScrDyn *scr_worker_new_options(double root, ScrStr *filename, ScrDyn *opt for (int i = 0; i < 2; i++) { view->stdio_captured[i] = (capture >> i) & 1; /* without the stream unit nothing can read the stream: the output is dropped */ - if (view->stdio_captured[i] && scr_worker_stdio_ops) view->stdio[i] = scr_worker_stdio_ops->readable(); + if (view->stdio_captured[i] && scr_worker_stdio_ops) + view->stdio[i] = scr_worker_stdio_ops->readable("ReadableWorkerStdio"); } view->stdin_captured = capture & 4; if (view->stdin_captured && scr_worker_stdio_ops) view->stdin_stream = scr_worker_stdin_new(thread); diff --git a/tests/corpus/5983-runtime-constructor.ts b/tests/corpus/5983-runtime-constructor.ts new file mode 100644 index 000000000..3fb9672d0 --- /dev/null +++ b/tests/corpus/5983-runtime-constructor.ts @@ -0,0 +1,48 @@ +// x.constructor on instances of EventEmitter and the stream classes: +// .name is the class Node reports (fs, zlib and program subclasses +// included), === compares with the instance's own class, and the value +// is the class object for EventEmitter and the stream classes. +import { EventEmitter } from "node:events"; +import { Duplex, PassThrough, Readable, Transform, Writable } from "node:stream"; +import * as fs from "node:fs"; +import * as zlib from "node:zlib"; + +class Bus extends EventEmitter {} + +const emitter = new EventEmitter(); +const bus: EventEmitter = new Bus(); +console.log("emitter", emitter.constructor === EventEmitter, emitter.constructor.name); +console.log("subclass", bus.constructor === EventEmitter, bus.constructor.name); + +const streams: Readable[] = [new Readable(), new Duplex(), new Transform(), new PassThrough()]; +for (const s of streams) { + console.log( + "stream", + s.constructor.name, + s.constructor === Readable, + s.constructor === Duplex, + s.constructor === Transform, + s.constructor === PassThrough, + s.constructor !== Readable, + ); +} + +const from = Readable.from(["a"]); +console.log("from", from.constructor.name, from.constructor === Readable); + +const readStream: Readable = fs.createReadStream(import.meta.filename); +const writeStream: Writable = fs.createWriteStream("/dev/null"); +console.log("fs", readStream.constructor.name, readStream.constructor === Readable); +console.log("fs", writeStream.constructor.name, writeStream.constructor === Writable); +readStream.destroy(); +writeStream.destroy(); + +const gzip: Transform = zlib.createGzip(); +const inflate: Transform = zlib.createInflateRaw(); +console.log("zlib", gzip.constructor.name, inflate.constructor.name, gzip.constructor === Transform); + +const kinds: EventEmitter[] = [new EventEmitter(), new Writable(), new PassThrough()]; +for (const k of kinds) { + const Kind = k.constructor as typeof EventEmitter; + console.log("value", Kind.name, Kind === EventEmitter, k instanceof Kind, new Kind() instanceof EventEmitter); +} diff --git a/tests/harness/corpus-costs/5983-runtime-constructor.ts.json b/tests/harness/corpus-costs/5983-runtime-constructor.ts.json new file mode 100644 index 000000000..5506cd9a8 --- /dev/null +++ b/tests/harness/corpus-costs/5983-runtime-constructor.ts.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 6900 }