mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-29 07:04:09 +08:00
Support zlib stream classes as const aliases and instanceof targets
`const C = zlib.Gzip` stopped at SC2020, and `instanceof zlib.Gzip` (any zlib class) stopped at SC1090. A const binding of a zlib class is now a compile-time alias with no runtime slot: `new C()`, `typeof C`, `C.name` and `C === zlib.Gzip` lower at the use. `instanceof` against a zlib class asks the runtime for the stream's mode when the operand is a Transform, tests for a Transform first when it is typed as one of Transform's bases, and answers false for other static types, as in Node. Other uses of an alias as a value report SC1090 at the use.
This commit is contained in:
@@ -848,6 +848,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"fetch.abortAny": "scr_fetch_abort_any",
|
||||
"fetch.webIs": "scr_fetch_web_is",
|
||||
"fetch.streamIs": "scr_fetch_stream_is",
|
||||
"zlib.streamIs": "scr_zlib_stream_is",
|
||||
"fetch.streamNew": "scr_fetch_stream_new",
|
||||
"fetch.streamFrom": "scr_fetch_stream_from",
|
||||
"fetch.readerRead": "scr_fetch_reader_read",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { lowerBuiltinByteInput, errorFirstBytesCallback } from "./arguments.js";
|
||||
import { dynUndefinedExpr, numLit } from "../../../ir/build.js";
|
||||
import { dynUndefinedExpr, numLit, varRef } from "../../../ir/build.js";
|
||||
import { InternalCompilerError } from "../../../errors.js";
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import { isNodeTypesPath } from "../../dts-paths.js";
|
||||
import {
|
||||
BOOL,
|
||||
BYTES_U8,
|
||||
DYN,
|
||||
F64,
|
||||
@@ -39,7 +40,8 @@ const ZLIB_CALLBACK_FNS: Readonly<Record<string, IrLibFn | undefined>> = {
|
||||
unzip: "zlib.unzipCb",
|
||||
};
|
||||
|
||||
const ZLIB_STREAM_T: IrType = { kind: "object", className: "%Transform" };
|
||||
const ZLIB_STREAM_CLASS = "%Transform";
|
||||
const ZLIB_STREAM_T: IrType = { kind: "object", className: ZLIB_STREAM_CLASS };
|
||||
|
||||
/** A zlib data argument, lowered once: Buffer/Uint8Array bytes, a string
|
||||
* (UTF-8 encoded here), or a checked-dynamic value (the JS lane's untyped
|
||||
@@ -130,6 +132,8 @@ export function zlibStreamModeOf(
|
||||
symbol: ts.Symbol | null | undefined,
|
||||
): number | null {
|
||||
if (!symbol) return null;
|
||||
const aliasMode = zlibClassAliasModeOf(lowerer, symbol, 0);
|
||||
if (aliasMode !== undefined) return aliasMode;
|
||||
const target =
|
||||
symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol;
|
||||
const mode = ZLIB_STREAM_MODES.get(target.name);
|
||||
@@ -494,3 +498,145 @@ export function lowerZlibModuleCall(
|
||||
}
|
||||
throw new InternalCompilerError(`unhandled lowered zlib member ${bi.member}`);
|
||||
}
|
||||
|
||||
/** The zlib stream class an expression names (`zlib.Gzip`, an imported
|
||||
* `Gzip`, a `const C = zlib.Gzip` alias), as its node_zlib_mode, or null. */
|
||||
export function zlibClassModeOfExpr(lowerer: Lowerer, expr: ts.Expression): number | null {
|
||||
let e = expr;
|
||||
while (ts.isParenthesizedExpression(e)) e = e.expression;
|
||||
if (ts.isIdentifier(e)) return zlibStreamModeOf(lowerer, lowerer.checker.getSymbolAtLocation(e));
|
||||
if (ts.isPropertyAccessExpression(e) && !e.questionDotToken) {
|
||||
return zlibStreamModeOf(lowerer, lowerer.checker.getSymbolAtLocation(e.name));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The class name of a zlib stream mode (Node's constructor name). */
|
||||
export function zlibClassName(mode: number): string {
|
||||
for (const [name, m] of ZLIB_STREAM_MODES) if (m === mode) return name;
|
||||
return "Zlib";
|
||||
}
|
||||
|
||||
/** The mode of a `const C = zlib.Gzip` binding (or an alias of such a
|
||||
* binding), null when the symbol is a const binding of something else,
|
||||
* undefined when it is not a const binding. Resolved from the declaration,
|
||||
* so uses lowered before the declaration statement see the same answer. */
|
||||
function zlibClassAliasModeOf(
|
||||
lowerer: Lowerer,
|
||||
symbol: ts.Symbol,
|
||||
depth: number,
|
||||
): number | null | undefined {
|
||||
const cached = lowerer.zlibClassAliases.get(symbol);
|
||||
if (cached !== undefined) return cached;
|
||||
if ((symbol.flags & ts.SymbolFlags.BlockScopedVariable) === 0) return undefined;
|
||||
const decl = lowerer.checker.valueDeclarationOf(symbol);
|
||||
if (decl === undefined || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name)) {
|
||||
return undefined;
|
||||
}
|
||||
// const only: a var or let binding could be reassigned
|
||||
const list = decl.parent;
|
||||
if (!ts.isVariableDeclarationList(list) || (list.flags & ts.NodeFlags.Const) === 0) {
|
||||
return undefined;
|
||||
}
|
||||
let init = decl.initializer;
|
||||
while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression;
|
||||
if (init === undefined || depth > 16) return null;
|
||||
let mode: number | null = null;
|
||||
if (ts.isIdentifier(init)) {
|
||||
const next = lowerer.checker.getSymbolAtLocation(init);
|
||||
if (next !== undefined) {
|
||||
const nested = zlibClassAliasModeOf(lowerer, next, depth + 1);
|
||||
mode = nested === undefined ? zlibStreamModeOf(lowerer, next) : nested;
|
||||
}
|
||||
} else if (ts.isPropertyAccessExpression(init) && !init.questionDotToken) {
|
||||
mode = zlibStreamModeOf(lowerer, lowerer.checker.getSymbolAtLocation(init.name));
|
||||
}
|
||||
if (mode !== null) lowerer.zlibClassAliases.set(symbol, mode);
|
||||
return mode;
|
||||
}
|
||||
|
||||
/** True for `const C = zlib.Gzip` (or an alias of an alias): a
|
||||
* compile-time zlib class alias that owns no runtime slot, so the
|
||||
* statement lowering skips it. */
|
||||
export function registerZlibClassAlias(
|
||||
lowerer: Lowerer,
|
||||
nameNode: ts.Node,
|
||||
init: ts.Expression | undefined,
|
||||
): boolean {
|
||||
if (!ts.isIdentifier(nameNode) || init === undefined) return false;
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(nameNode);
|
||||
return symbol !== undefined && zlibClassAliasOf(lowerer, symbol) !== null;
|
||||
}
|
||||
|
||||
/** The mode of a `const C = zlib.Gzip` alias symbol, or null. */
|
||||
export function zlibClassAliasOf(lowerer: Lowerer, symbol: ts.Symbol): number | null {
|
||||
return zlibClassAliasModeOf(lowerer, symbol, 0) ?? null;
|
||||
}
|
||||
|
||||
/** `value instanceof zlib.Gzip` (any spelling of the class, aliases
|
||||
* included). zlib streams are runtime Transforms: a Transform operand asks
|
||||
* the runtime for the stream's mode, and an operand typed as one of
|
||||
* Transform's bases (Duplex, Readable, EventEmitter) is first tested for a
|
||||
* Transform. Any other static type answers false after the operand
|
||||
* evaluates. */
|
||||
export function lowerZlibInstanceOf(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.BinaryExpression,
|
||||
mode: number,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const value = lowerer.lowerExpr(expr.left);
|
||||
const type = value.type;
|
||||
const holdsStream = (className: string): boolean =>
|
||||
className === ZLIB_STREAM_CLASS || lowerer.isSubclassOf(ZLIB_STREAM_CLASS, className);
|
||||
const arms = type.kind === "union" ? lowerer.unions.get(type.unionId)?.arms : undefined;
|
||||
if (
|
||||
type.kind === "dyn" ||
|
||||
arms?.some((arm) => arm.kind === "object" && holdsStream(arm.className))
|
||||
) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
expr,
|
||||
`'instanceof zlib.${zlibClassName(mode)}' on an untyped or union value (give the operand a stream type first)`,
|
||||
);
|
||||
}
|
||||
if (type.kind === "object" && holdsStream(type.className)) {
|
||||
const isMode = (stream: IrExpr): IrExpr => ({
|
||||
kind: "libCall",
|
||||
fn: "zlib.streamIs",
|
||||
args: [stream, numLit(mode, loc)],
|
||||
type: BOOL,
|
||||
loc,
|
||||
});
|
||||
if (type.className === ZLIB_STREAM_CLASS) return isMode(value);
|
||||
const local = lowerer.declareHiddenLocal("%zlibTest", type);
|
||||
const ref = varRef(local.id, type, loc);
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }],
|
||||
result: {
|
||||
kind: "logical",
|
||||
op: "&&",
|
||||
left: {
|
||||
kind: "instanceOf",
|
||||
value: ref,
|
||||
className: ZLIB_STREAM_CLASS,
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
right: isMode({ kind: "downcast", value: ref, type: ZLIB_STREAM_T, loc }),
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "exprStmt", expr: value, loc }],
|
||||
result: { kind: "boolLit", value: false, type: BOOL, loc },
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { InternalCompilerError } from "../../errors.js";
|
||||
* hierarchy registration. */
|
||||
import { fsStreamClassOf, lowerFsStreamNew } from "./builtins/fs-streams.js";
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import { lowerZlibStreamNew, zlibStreamModeOf } from "./builtins/compression.js";
|
||||
import { lowerZlibStreamNew, zlibClassName, zlibStreamModeOf } from "./builtins/compression.js";
|
||||
import type { FnCtx, Lowerer } from "./lowerer.js";
|
||||
import {
|
||||
BOOL,
|
||||
@@ -8664,7 +8664,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
// `new zlib.Gzip(options?)` — the zlib stream classes.
|
||||
const zlibModeNs = zlibStreamModeOf(lowerer, resolved);
|
||||
if (zlibModeNs !== null) {
|
||||
const what = `new zlib.${expr.expression.name.text}`;
|
||||
const what = `new zlib.${zlibClassName(zlibModeNs)}`;
|
||||
return lowerZlibStreamNew(lowerer, expr.arguments ?? [], zlibModeNs, what, expr, loc);
|
||||
}
|
||||
// `new N.C(...)` / `new a.Point(...)` — construction through a
|
||||
@@ -8922,7 +8922,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
if (streamInfo) return lowerStreamNew(lowerer, expr, streamInfo);
|
||||
const zlibMode = zlibStreamModeOf(lowerer, symbol);
|
||||
if (zlibMode !== null) {
|
||||
const what = `new zlib.${symbol!.name}`;
|
||||
const what = `new zlib.${zlibClassName(zlibMode)}`;
|
||||
return lowerZlibStreamNew(lowerer, expr.arguments ?? [], zlibMode, what, expr, loc);
|
||||
}
|
||||
// `new URL(input)`: the WHATWG URL class (stdlib/@types provenance —
|
||||
|
||||
@@ -31,7 +31,14 @@ import { literalUnionArm } from "../union-discriminants.js";
|
||||
* (FieldTarget). */
|
||||
import { isFsStreamMember, lowerFsStreamProperty } from "./builtins/fs-streams.js";
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import { isZlibStreamMember, lowerZlibStreamBytesWritten } from "./builtins/compression.js";
|
||||
import {
|
||||
isZlibStreamMember,
|
||||
lowerZlibInstanceOf,
|
||||
lowerZlibStreamBytesWritten,
|
||||
zlibClassAliasOf,
|
||||
zlibClassModeOfExpr,
|
||||
zlibClassName,
|
||||
} from "./builtins/compression.js";
|
||||
import { constituentTypes } from "../ts7/checker.js";
|
||||
import { dirname } from "node:path";
|
||||
import * as posix from "node:path/posix";
|
||||
@@ -608,6 +615,10 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
if (isNodeModuleValue(lowerer, expr.expression)) {
|
||||
return { kind: "strLit", value: "object", type: STRING, loc };
|
||||
}
|
||||
// `typeof zlib.Gzip`, `typeof C` over a zlib class alias.
|
||||
if (zlibClassModeOfExpr(lowerer, expr.expression) !== null) {
|
||||
return { kind: "strLit", value: "function", type: STRING, loc };
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(expr.expression)) {
|
||||
const member = expr.expression;
|
||||
const prototype = member.expression;
|
||||
@@ -864,6 +875,15 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
`a builtin function alias as an escaping value (call '${expr.text}' directly)`,
|
||||
);
|
||||
}
|
||||
// `const C = zlib.Gzip` owns no runtime slot: construction,
|
||||
// instanceof, typeof and .name lower at their use sites.
|
||||
if (sym && zlibClassAliasOf(lowerer, sym) !== null) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
expr,
|
||||
`a zlib class alias as an escaping value (use '${expr.text}' with new, instanceof, typeof or .name)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Union-typed bindings read through tsc's control-flow narrowing:
|
||||
// when the checker types this USE as a single arm, maybeNarrow
|
||||
@@ -1799,6 +1819,11 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
const table = lowerInternTableProperty(lowerer, expr);
|
||||
if (table) return table;
|
||||
}
|
||||
// `zlib.Gzip.name`, `C.name` over a zlib class alias.
|
||||
if (expr.name.text === "name" && !expr.questionDotToken) {
|
||||
const mode = zlibClassModeOfExpr(lowerer, expr.expression);
|
||||
if (mode !== null) return { kind: "strLit", value: zlibClassName(mode), type: STRING, loc };
|
||||
}
|
||||
// Expando statics may still be absent even when JS inference sees
|
||||
// the eventual assignment's scalar type. Preserve the checked value.
|
||||
const staticField = lowerStaticFieldRead(lowerer, expr);
|
||||
@@ -10017,6 +10042,19 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
|
||||
lowerUnionTypeofTest(lowerer, expr, loc);
|
||||
if (test) return test;
|
||||
}
|
||||
// `C === zlib.Gzip` between two zlib class references: the classes
|
||||
// are compile-time values, so the answer folds.
|
||||
if (
|
||||
op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
||||
op === ts.SyntaxKind.ExclamationEqualsEqualsToken
|
||||
) {
|
||||
const leftClass = zlibClassModeOfExpr(lowerer, expr.left);
|
||||
const rightClass = leftClass === null ? null : zlibClassModeOfExpr(lowerer, expr.right);
|
||||
if (leftClass !== null && rightClass !== null) {
|
||||
const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
|
||||
return { kind: "boolLit", value: (leftClass === rightClass) !== negated, type: BOOL, loc };
|
||||
}
|
||||
}
|
||||
|
||||
const strictEquality =
|
||||
op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
||||
@@ -12062,6 +12100,8 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc
|
||||
// h2 compat 'connect' narrowing (lower-server.ts): a union tag test.
|
||||
const sockTest = lowerSocketInstanceOf(lowerer, expr, loc);
|
||||
if (sockTest !== null) return sockTest;
|
||||
const zlibMode = zlibClassModeOfExpr(lowerer, expr.right);
|
||||
if (zlibMode !== null) return lowerZlibInstanceOf(lowerer, expr, zlibMode, loc);
|
||||
|
||||
if (lowerer.isStdlibGlobal(expr.right, "Set") || lowerer.isStdlibGlobal(expr.right, "Map")) {
|
||||
const collection = lowerer.isStdlibGlobal(expr.right, "Map") ? "map" : "set";
|
||||
|
||||
@@ -80,6 +80,7 @@ import {
|
||||
createRequireProgramModuleDecl,
|
||||
} from "./builtins/module-bindings.js";
|
||||
import { isPromisifyCall, registerBuiltinCallableAlias } from "./builtins/callable-aliases.js";
|
||||
import { registerZlibClassAlias } from "./builtins/compression.js";
|
||||
import { stripTypeCasts } from "./builtins/arguments.js";
|
||||
import {
|
||||
bindingContextualGenericFnNodeOf,
|
||||
@@ -1700,6 +1701,7 @@ export function collectGlobals(
|
||||
// resolution and value reads reuse the source's interned closure.
|
||||
if (isConst && registerOverloadedCallableAlias(lowerer, decl)) continue;
|
||||
if (isConst && registerBuiltinCallableAlias(lowerer, decl.name, decl.initializer)) continue;
|
||||
if (registerZlibClassAlias(lowerer, decl.name, decl.initializer)) continue;
|
||||
// `const f = (x) => ...` at file scope of an npm-static JS file
|
||||
// whose params are implicit-any: the implicit-monomorphization
|
||||
// twin of the generic binding above — no global exists; calls and
|
||||
|
||||
@@ -147,6 +147,7 @@ import {
|
||||
} from "./builtins/module-bindings.js";
|
||||
import { lowerNodeModuleCall } from "./builtins/module-resolution.js";
|
||||
import { registerBuiltinCallableAlias } from "./builtins/callable-aliases.js";
|
||||
import { registerZlibClassAlias } from "./builtins/compression.js";
|
||||
import { lowerEnumDeclaration } from "./lower-enums.js";
|
||||
import {
|
||||
abstractPropertyDeclOf,
|
||||
@@ -4263,6 +4264,7 @@ export function lowerVarDecl(
|
||||
// reads reuse its interned closure identity.
|
||||
if (!isLet && registerOverloadedCallableAlias(lowerer, decl)) return null;
|
||||
if (!isLet && registerBuiltinCallableAlias(lowerer, decl.name, decl.initializer)) return null;
|
||||
if (registerZlibClassAlias(lowerer, decl.name, decl.initializer)) return null;
|
||||
|
||||
// `const knownBy = (cmd) => ...` — an IMPLICIT-ANY function-value
|
||||
// binding in npm-static JS (function-scope bindings register here, in
|
||||
|
||||
@@ -1802,6 +1802,11 @@ export class Lowerer {
|
||||
* typeParamBindings — the checker has no `T` to substitute, so the
|
||||
* binding rides the node-type accessor instead of mapType. */
|
||||
implicitParamTypes: Map<ts.Symbol, ts.Type> | null = null;
|
||||
/** Immutable bindings that name a zlib stream class (`const C =
|
||||
* zlib.Gzip`), keyed by the binding's symbol: the class's
|
||||
* node_zlib_mode. No runtime slot exists: new, typeof, .name and ===
|
||||
* resolve at compile time, and instanceof checks the stream's mode. */
|
||||
readonly zlibClassAliases = new Map<ts.Symbol, number>();
|
||||
/** Whether some net.Socket in this program may get an encoding: a
|
||||
* setEncoding call whose receiver is a socket or an untyped value.
|
||||
* Computed once on first use (null until then). */
|
||||
|
||||
@@ -2771,6 +2771,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
* receivers are borrowed. */
|
||||
"zlib.streamNew": { argTypes: [F64, DYN], result: VOID },
|
||||
"zlib.streamBytesWritten": { argTypes: [null], result: F64 },
|
||||
/** `stream instanceof zlib.Gzip`: the borrowed stream and the class's
|
||||
* node_zlib_mode; true when the stream is a zlib stream of that mode. */
|
||||
"zlib.streamIs": { argTypes: [null, F64], result: BOOL },
|
||||
"zlib.streamFlush": { argTypes: [null, DYN], result: VOID },
|
||||
"zlib.streamFlushCb": { argTypes: [null, DYN, null], result: VOID },
|
||||
"zlib.streamParams": { argTypes: [null, DYN, DYN], result: VOID },
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5975-zlib-class-values.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/zlib-class-alias-escape.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -90,6 +90,9 @@ ScrPromise *scr_zlib_codec_promise_dyn(ScrDyn *data, double mode, ScrDyn *opts);
|
||||
* with Node's ladder. The callbacks MOVE in and may be NULL. */
|
||||
ScrStream *scr_zlib_stream_new(double mode, ScrDyn *opts);
|
||||
double scr_zlib_stream_bytes_written(ScrStream *s);
|
||||
/* `s instanceof zlib.Gzip` (and the other classes): true when s is a zlib
|
||||
* stream built with that mode. s is borrowed. */
|
||||
bool scr_zlib_stream_is(ScrStream *s, double mode);
|
||||
void scr_zlib_stream_flush(ScrStream *s, ScrDyn *kind, ScrClosure *cb);
|
||||
void scr_zlib_stream_params(ScrStream *s, ScrDyn *level, ScrDyn *strategy, ScrClosure *cb);
|
||||
void scr_zlib_stream_close(ScrStream *s, ScrClosure *cb);
|
||||
|
||||
@@ -496,6 +496,11 @@ static ScrZlibStream *scr_zs_from_stream(ScrStream *s) {
|
||||
return zs;
|
||||
}
|
||||
|
||||
bool scr_zlib_stream_is(ScrStream *s, double mode) {
|
||||
const ScrZlibStream *zs = scr_stream_native(s, &scr_zs_release_v);
|
||||
return zs != NULL && zs->mode == (int)mode;
|
||||
}
|
||||
|
||||
double scr_zlib_stream_bytes_written(ScrStream *s) {
|
||||
ScrZlibStream *zs = scr_zs_from_stream(s);
|
||||
return zs ? zs->bytes_written : 0;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// The zlib stream classes as values: const aliases construct, instanceof
|
||||
// tests each class against stream, EventEmitter and plain operands, and
|
||||
// typeof, .name and === answer as in Node.
|
||||
import * as zlib from "node:zlib";
|
||||
import { Gunzip, Unzip } from "node:zlib";
|
||||
import { Readable, Transform } from "node:stream";
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
function makeLate() {
|
||||
return new Late();
|
||||
}
|
||||
|
||||
const Gz = zlib.Gzip;
|
||||
const Gun = Gunzip;
|
||||
const Again = Gz;
|
||||
const Late = zlib.Inflate;
|
||||
|
||||
const gz = new Gz();
|
||||
console.log("new alias", gz instanceof zlib.Gzip, gz instanceof Gz, gz instanceof Again);
|
||||
console.log("other classes", gz instanceof Gun, gz instanceof Gunzip, gz instanceof zlib.Deflate);
|
||||
console.log("factory", zlib.createGzip() instanceof Gz, zlib.createGunzip() instanceof Gz);
|
||||
console.log("hoisted use", makeLate() instanceof zlib.Inflate, makeLate() instanceof Gun);
|
||||
|
||||
const asTransform: Transform = new zlib.DeflateRaw();
|
||||
console.log("transform-typed", asTransform instanceof zlib.DeflateRaw, asTransform instanceof Gz);
|
||||
const plain = new Transform({
|
||||
transform(chunk, _encoding, callback) {
|
||||
callback(null, chunk);
|
||||
},
|
||||
});
|
||||
console.log("plain transform", plain instanceof Gz, plain instanceof Transform);
|
||||
const readable = new Readable({ read() {} });
|
||||
console.log("readable", readable instanceof zlib.Unzip);
|
||||
|
||||
const emitter: EventEmitter = zlib.createDeflate();
|
||||
console.log("emitter-typed", emitter instanceof zlib.Deflate, emitter instanceof zlib.Inflate);
|
||||
const bare = new EventEmitter();
|
||||
console.log("bare emitter", bare instanceof zlib.Deflate);
|
||||
const record = { kind: "gzip" };
|
||||
console.log("object", record instanceof Gz);
|
||||
|
||||
console.log("typeof", typeof Gz, typeof zlib.Inflate, typeof Unzip);
|
||||
console.log("name", Gz.name, Again.name, Gun.name, zlib.DeflateRaw.name, Unzip.name);
|
||||
console.log("identity", Gz === zlib.Gzip, Again === Gz, Gz === Gun, Gz !== zlib.Gunzip);
|
||||
|
||||
const chunks: Buffer[] = [];
|
||||
const roundTrip = new Again();
|
||||
roundTrip.on("data", (chunk: Buffer) => chunks.push(chunk));
|
||||
roundTrip.on("end", () => {
|
||||
console.log("round trip", zlib.gunzipSync(Buffer.concat(chunks)).toString());
|
||||
});
|
||||
roundTrip.end("class values");
|
||||
@@ -0,0 +1,11 @@
|
||||
// A zlib class alias owns no runtime value: construction, instanceof,
|
||||
// typeof and .name compile, other uses report at the use. instanceof on
|
||||
// an untyped operand reports as well.
|
||||
import * as zlib from "node:zlib";
|
||||
|
||||
const Gz = zlib.Gzip;
|
||||
const classes = [Gz];
|
||||
console.log(classes.length);
|
||||
|
||||
const parsed: unknown = JSON.parse("{}");
|
||||
console.log(parsed instanceof Gz);
|
||||
@@ -0,0 +1,13 @@
|
||||
zlib-class-alias-escape.ts:7:18 - error SC1090: a zlib class alias as an escaping value (use 'Gz' with new, instanceof, typeof or .name) is not supported yet
|
||||
|
||||
6 | const Gz = zlib.Gzip;
|
||||
7 | const classes = [Gz];
|
||||
| ^~
|
||||
8 | console.log(classes.length);
|
||||
|
||||
zlib-class-alias-escape.ts:11:13 - error SC1090: 'instanceof zlib.Gzip' on an untyped or union value (give the operand a stream type first) is not supported yet
|
||||
|
||||
10 | const parsed: unknown = JSON.parse("{}");
|
||||
11 | console.log(parsed instanceof Gz);
|
||||
| ^~~~~~~~~~~~~~~~~~~~
|
||||
12 |
|
||||
@@ -1916,6 +1916,7 @@
|
||||
"5971-buffer-chunks-to-string.ts": 2700,
|
||||
"5972-net-set-encoding-listeners.ts": 2550,
|
||||
"5974-typeof-module-exports.cjs": 2000,
|
||||
"5975-zlib-class-values.ts": 2800,
|
||||
"5990-worker-uncloneable.mjs": 3283,
|
||||
"5991-worker-async-dispose.mjs": 5699,
|
||||
"5992-worker-thread-name-lifetime.mjs": 2687,
|
||||
@@ -4620,6 +4621,7 @@
|
||||
"5971-buffer-chunks-to-string.ts": 3100,
|
||||
"5972-net-set-encoding-listeners.ts": 3000,
|
||||
"5974-typeof-module-exports.cjs": 2500,
|
||||
"5975-zlib-class-values.ts": 4200,
|
||||
"5990-worker-uncloneable.mjs": 22432,
|
||||
"5991-worker-async-dispose.mjs": 25768,
|
||||
"5992-worker-thread-name-lifetime.mjs": 28932,
|
||||
|
||||
Reference in New Issue
Block a user