mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Read x.constructor on EventEmitter and stream instances
`x.constructor` on a value typed as EventEmitter or a stream class stopped at SC2020. The runtime now records each instance's Node class name: fs streams as ReadStream and WriteStream, child process stdio as Socket, worker stdio as ReadableWorkerStdio and WritableWorkerStdio, and Hash/Hmac stream views as Hash and Hmac (zlib streams, the base classes and program subclasses already did). The name is the one Node's leak warning and argument-type errors print, so those messages now name the same class as Node. `x.constructor.name` reads that name (scr_emitter_class_name). `x.constructor === C` for EventEmitter or a stream class tests the instance's own class: C's interval, none of its direct subclasses', and C's name, as fs, zlib, crypto, child process and worker streams share the base classes' vtables. `x.constructor` as a value is the class object of EventEmitter or the stream class the instance belongs to; for instances of the other runtime classes and of program subclasses it reports SC1090 at run time.
This commit is contained in:
@@ -994,6 +994,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
// 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",
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 },
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5983-runtime-constructor.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3500, "san": 6900 }
|
||||
Reference in New Issue
Block a user