mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Use classes that extend EventEmitter or a stream class as values (#1479)
This commit is contained in:
@@ -18,6 +18,7 @@ import type { Lowerer } from "./lowerer.js";
|
||||
import type { ParamShape } from "./call-signatures.js";
|
||||
import { classPrototypeData } from "./class-prototypes.js";
|
||||
import { classInstanceOf } from "./class-dynamic-dispatch.js";
|
||||
import { inheritedBuiltinCtorFence } from "./lower-classes.js";
|
||||
|
||||
const PREFIX = "%class.construct:";
|
||||
const PACKED = `${PREFIX}packed`;
|
||||
@@ -380,6 +381,29 @@ export class ClassConstructionDispatch {
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
const type: IrType = { kind: "classval", className: name };
|
||||
const callee = varRef("p.0", DYN, loc);
|
||||
// A constructor inherited from a runtime stream or EventEmitter
|
||||
// takes no arguments here: arguments report at run time, as they do
|
||||
// at compile time for typed construction.
|
||||
const fence = inheritedBuiltinCtorFence(lowerer, info, loc);
|
||||
if (fence !== null && fn.name !== PACKED && fn.params.length > 1) {
|
||||
fn.body.unshift({
|
||||
kind: "if",
|
||||
cond: {
|
||||
kind: "libCall",
|
||||
fn: "dyn.classIs",
|
||||
args: [callee, { kind: "strLit", value: typeKey(type), type: STRING, loc }],
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
then: [fence],
|
||||
else_: null,
|
||||
loc,
|
||||
});
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
const args = constructorArguments(
|
||||
lowerer,
|
||||
info.ctorParams,
|
||||
@@ -388,8 +412,6 @@ export class ClassConstructionDispatch {
|
||||
fn.name === PACKED ? varRef("p.1", DYN, loc) : undefined,
|
||||
);
|
||||
if (!args) continue;
|
||||
const type: IrType = { kind: "classval", className: name };
|
||||
const callee = varRef("p.0", DYN, loc);
|
||||
const value = constructedValue(lowerer, info, {
|
||||
kind: "newValue",
|
||||
callee: { kind: "dynCheck", value: callee, type, loc },
|
||||
@@ -400,6 +422,28 @@ export class ClassConstructionDispatch {
|
||||
const body: IrStmt[] = [
|
||||
{ kind: "return", value: lowerer.coerceToExpected(value, DYN), loc },
|
||||
];
|
||||
// A spread call's argument count is known only at run time.
|
||||
if (fence !== null && fn.name === PACKED)
|
||||
body.unshift({
|
||||
kind: "if",
|
||||
cond: {
|
||||
kind: "bin",
|
||||
op: ">",
|
||||
left: {
|
||||
kind: "libCall",
|
||||
fn: "dyn.arrLen",
|
||||
args: [varRef("p.1", DYN, loc)],
|
||||
type: F64,
|
||||
loc,
|
||||
},
|
||||
right: { kind: "numLit", value: 0, type: F64, loc },
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
then: [fence],
|
||||
else_: null,
|
||||
loc,
|
||||
});
|
||||
fn.body.unshift({
|
||||
kind: "if",
|
||||
cond: {
|
||||
|
||||
@@ -2213,11 +2213,7 @@ export function collectClassShapeInner(
|
||||
}
|
||||
if (lowerer.collectingExprClasses.has(decl)) {
|
||||
for (let parent = base; parent; parent = parent.base) {
|
||||
if (
|
||||
parent.builtinEmitter ||
|
||||
parent.builtinStream !== undefined ||
|
||||
parent.builtinEventTarget !== undefined
|
||||
) {
|
||||
if (parent.builtinEventTarget !== undefined) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
decl,
|
||||
@@ -5183,16 +5179,11 @@ export function classValueRef(lowerer: Lowerer, info: ClassInfo, blame: ts.Node)
|
||||
loc: locOf(blame),
|
||||
};
|
||||
}
|
||||
if (
|
||||
lowerer.inheritsBuiltinEmitterCtor(info) ||
|
||||
inheritsBuiltinStreamCtor(lowerer, info) ||
|
||||
eventTargetRootOf(info) !== null ||
|
||||
(() => {
|
||||
for (let c = info.base; c; c = c.base)
|
||||
if (c.builtinEmitter || c.builtinStream !== undefined) return true;
|
||||
return false;
|
||||
})()
|
||||
) {
|
||||
// Classes extending EventEmitter or a stream class construct through
|
||||
// their own (or synthesized) `%C.constructor`, whose super call runs the
|
||||
// runtime initializer, so the class object's construct thunk serves them
|
||||
// like any program class. EventTarget subclasses keep the refusal.
|
||||
if (eventTargetRootOf(info) !== null) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
blame,
|
||||
@@ -8737,6 +8728,60 @@ function inheritsBuiltinStreamCtor(lowerer: Lowerer, info: ClassInfo): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** The run-time form of refuseInheritedBuiltinCtorArgs, for construction
|
||||
* from checked-dynamic code (`new F(options)` with F an untyped value
|
||||
* holding C), whose argument count is known only to the caller: a
|
||||
* runtimeFence located at C, or null when C's constructor takes its
|
||||
* arguments. */
|
||||
export function inheritedBuiltinCtorFence(
|
||||
lowerer: Lowerer,
|
||||
info: ClassInfo,
|
||||
loc: SrcLoc,
|
||||
): IrStmt | null {
|
||||
let feature: string | null = null;
|
||||
if (inheritsBuiltinStreamCtor(lowerer, info))
|
||||
feature = `new ${info.def.name.replace(/^%/, "")} with arguments through an inherited stream constructor (declare a constructor that passes an inline options object to super(...))`;
|
||||
else if (lowerer.inheritsBuiltinEmitterCtor(info))
|
||||
feature = "EventEmitter constructor options ('captureRejections')";
|
||||
if (feature === null) return null;
|
||||
// Located at the class, whose constructor takes no arguments.
|
||||
const at = info.decl !== null ? locOf(info.decl) : loc;
|
||||
const diag = unsupportedDiag("SC1090", at, feature);
|
||||
lowerer.runtimeFences.push(diag);
|
||||
const line =
|
||||
info.decl !== null
|
||||
? ts.getLineAndCharacterOfPosition(info.decl.getSourceFile(), at.start).line + 1
|
||||
: null;
|
||||
const where = line !== null ? ` [${diag.code} at ${at.file}:${line}]` : "";
|
||||
return { kind: "runtimeFence", code: diag.code, message: `${diag.message}${where}`, loc };
|
||||
}
|
||||
|
||||
/** `new C(args)` where C's constructor is the inherited runtime stream or
|
||||
* EventEmitter constructor (a ctor-less chain into the base): that
|
||||
* constructor takes zero arguments here (the options bag fences, like the
|
||||
* super() form), so arguments report at the first one. Applies to direct
|
||||
* construction and to construction through a class value of C. Stream
|
||||
* subclasses come first: their chain roots at the emitter too, but the
|
||||
* message should name the stream story. */
|
||||
function refuseInheritedBuiltinCtorArgs(
|
||||
lowerer: Lowerer,
|
||||
info: ClassInfo,
|
||||
expr: ts.NewExpression,
|
||||
): void {
|
||||
const first = expr.arguments?.nodes[0];
|
||||
if (first === undefined) return;
|
||||
if (inheritsBuiltinStreamCtor(lowerer, info)) {
|
||||
lowerer.noLowering(
|
||||
`new ${info.def.name.replace(/^%/, "")} with arguments through an inherited stream constructor`,
|
||||
first,
|
||||
"declare a constructor that passes an inline options object to super(...)",
|
||||
);
|
||||
}
|
||||
if (lowerer.inheritsBuiltinEmitterCtor(info)) {
|
||||
lowerer.unsupported("SC1090", first, "EventEmitter constructor options ('captureRejections')");
|
||||
}
|
||||
}
|
||||
|
||||
/** Preserve argument evaluation order and option presence through builtin
|
||||
* and inherited Error constructors. Checked-dynamic conversion retains
|
||||
* Error identity and distinguishes a missing cause from undefined. */
|
||||
@@ -8861,24 +8906,7 @@ function lowerProgramClassNew(
|
||||
"construction through a function-local class's internal name",
|
||||
);
|
||||
lowerer.noteEdge(`%${info.def.name}.constructor`);
|
||||
// A ctor-less chain into an EventEmitter base inherits `new C()` —
|
||||
// zero arguments (the options bag fences, like the super() form).
|
||||
// Stream subclasses come first: their chain roots at the emitter
|
||||
// too, but the message should name the stream story.
|
||||
if (inheritsBuiltinStreamCtor(lowerer, info) && (expr.arguments?.nodes ?? []).length > 0) {
|
||||
lowerer.noLowering(
|
||||
`new ${info.def.name.replace(/^%/, "")} with arguments through an inherited stream constructor`,
|
||||
expr.arguments!.nodes[0]!,
|
||||
"declare a constructor that passes an inline options object to super(...)",
|
||||
);
|
||||
}
|
||||
if (lowerer.inheritsBuiltinEmitterCtor(info) && (expr.arguments?.nodes ?? []).length > 0) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
expr.arguments!.nodes[0]!,
|
||||
"EventEmitter constructor options ('captureRejections')",
|
||||
);
|
||||
}
|
||||
refuseInheritedBuiltinCtorArgs(lowerer, info, expr);
|
||||
// A ctor-less chain into a builtin error base inherits both arguments;
|
||||
// each synthesized constructor forwards the same pair to its base.
|
||||
const args = lowerer.inheritsBuiltinErrorCtor(info)
|
||||
@@ -10126,6 +10154,9 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
`EventEmitter and stream class values constructed with options (construct with 'new ${info.def.name.slice(1)}({ ... })' directly)`,
|
||||
);
|
||||
}
|
||||
// A program class whose constructor is the inherited runtime one
|
||||
// takes no arguments through its class value either.
|
||||
refuseInheritedBuiltinCtorArgs(lowerer, info, expr);
|
||||
defineClassValueConstruct(lowerer, info);
|
||||
const args = lowerer.completeArgs(
|
||||
expr.arguments?.nodes ?? [],
|
||||
|
||||
@@ -3674,8 +3674,10 @@ export function lowerStreamMethodCall(
|
||||
: lowerer.lowerExprExpecting(args[0]!, STRING);
|
||||
const fn = kind === "bytes" ? "writable.write" : "writable.writeStr";
|
||||
if (cbArg !== undefined) {
|
||||
const cb = lowerer.lowerExprExpecting(cbArg, funcOf([], VOID));
|
||||
if (cb.type.kind !== "func" || cb.type.params.length > 0) {
|
||||
const cb = callbackDeclaresParameters(cbArg)
|
||||
? null
|
||||
: lowerer.lowerExprExpecting(cbArg, funcOf([], VOID));
|
||||
if (cb === null || cb.type.kind !== "func" || cb.type.params.length > 0) {
|
||||
lowerer.noLowering(
|
||||
"write completion callbacks taking the error argument",
|
||||
cbArg,
|
||||
@@ -3732,8 +3734,10 @@ export function lowerStreamMethodCall(
|
||||
);
|
||||
}
|
||||
if (cbArg !== undefined) {
|
||||
const cb = lowerer.lowerExprExpecting(cbArg, funcOf([], VOID));
|
||||
if (cb.type.kind !== "func" || cb.type.params.length > 0) {
|
||||
const cb = callbackDeclaresParameters(cbArg)
|
||||
? null
|
||||
: lowerer.lowerExprExpecting(cbArg, funcOf([], VOID));
|
||||
if (cb === null || cb.type.kind !== "func" || cb.type.params.length > 0) {
|
||||
lowerer.noLowering(
|
||||
"end callbacks with parameters",
|
||||
cbArg,
|
||||
@@ -3998,3 +4002,17 @@ export function lowerStreamProperty(
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Whether an inline completion callback declares a parameter. Node calls
|
||||
* write and end callbacks with an error argument (null on success), but the
|
||||
* native callback receives none, so the parameter would read undefined
|
||||
* where Node passes null or the error. A function literal with parameters
|
||||
* would otherwise adapt to the zero-parameter slot silently. */
|
||||
function callbackDeclaresParameters(node: ts.Expression): boolean {
|
||||
let callback = node;
|
||||
while (ts.isParenthesizedExpression(callback)) callback = callback.expression;
|
||||
return (
|
||||
(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) &&
|
||||
callback.parameters.nodes.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6450-builtin-base-class-values.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6451-builtin-base-class-expressions.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/builtin-base-class-value-arguments.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/stream-completion-callback-error.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Classes extending Node builtin bases (Writable, Transform, EventEmitter)
|
||||
// used as values: through a variable, a generic factory, an object table and
|
||||
// an array, with instanceof against the class and its base.
|
||||
import { EventEmitter } from "node:events";
|
||||
import { Readable, Transform, Writable } from "node:stream";
|
||||
|
||||
class Sink extends Writable {
|
||||
readonly lines: string[] = [];
|
||||
private readonly tag: string;
|
||||
constructor(tag: string) {
|
||||
super();
|
||||
this.tag = tag;
|
||||
}
|
||||
_write(chunk: Buffer, _enc: BufferEncoding, cb: (err?: Error | null) => void): void {
|
||||
this.lines.push(this.tag + chunk.toString());
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
class Upper extends Transform {
|
||||
_transform(chunk: Buffer, _enc: BufferEncoding, cb: (err?: Error | null, data?: string) => void): void {
|
||||
cb(null, chunk.toString().toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
class Ticker extends EventEmitter {
|
||||
count = 0;
|
||||
tick(): void {
|
||||
this.count++;
|
||||
this.emit("tick", this.count);
|
||||
}
|
||||
}
|
||||
|
||||
class Plain extends EventEmitter {}
|
||||
|
||||
function build<T>(ctor: new () => T): T {
|
||||
return new ctor();
|
||||
}
|
||||
|
||||
const SinkClass = Sink;
|
||||
const s = new SinkClass("> ");
|
||||
s.write("a");
|
||||
s.end("b", () => console.log("sink", JSON.stringify(s.lines), s instanceof Sink, s instanceof Writable));
|
||||
|
||||
const t = build(Ticker);
|
||||
t.on("tick", (n: number) => console.log("tick", n));
|
||||
t.tick();
|
||||
t.tick();
|
||||
console.log("ticker", t instanceof Ticker, t instanceof EventEmitter, t.count);
|
||||
|
||||
const p = build(Plain);
|
||||
p.on("x", () => console.log("plain x"));
|
||||
p.emit("x");
|
||||
|
||||
const registry = { upper: Upper };
|
||||
const u = new registry.upper();
|
||||
u.on("data", (d: Buffer) => console.log("upper", d.toString()));
|
||||
u.end("hello");
|
||||
|
||||
const kinds = [Ticker, Ticker];
|
||||
console.log("names", kinds.map((k) => k.name).join(","), typeof Sink, Sink.name);
|
||||
const r = Readable.from(["x"]);
|
||||
console.log("readable", r instanceof Readable);
|
||||
@@ -0,0 +1,46 @@
|
||||
// Class expressions extending Node builtin bases (a bundler's output shape).
|
||||
"use strict";
|
||||
const { Readable, Transform } = require("stream");
|
||||
const { EventEmitter } = require("events");
|
||||
|
||||
const Wrap = class extends Readable {
|
||||
constructor(parts) {
|
||||
super();
|
||||
this.parts = parts;
|
||||
}
|
||||
_read() {
|
||||
const next = this.parts.shift();
|
||||
this.push(next === undefined ? null : next);
|
||||
}
|
||||
};
|
||||
const Bus = class extends EventEmitter {};
|
||||
const Lower = class extends Transform {
|
||||
_transform(chunk, _enc, cb) {
|
||||
cb(null, chunk.toString().toLowerCase());
|
||||
}
|
||||
};
|
||||
|
||||
const r = new Wrap(["a", "b"]);
|
||||
const chunks = [];
|
||||
r.on("data", (c) => chunks.push(c.toString()));
|
||||
r.on("end", () => console.log("wrap", chunks.join("+"), r instanceof Readable));
|
||||
const bus = new Bus();
|
||||
bus.on("ping", (n) => console.log("ping", n));
|
||||
bus.emit("ping", 1);
|
||||
const l = new Lower();
|
||||
l.on("data", (d) => console.log("lower", d.toString()));
|
||||
l.end("HeLLo");
|
||||
console.log(Wrap.name, Bus.name, typeof Lower);
|
||||
{
|
||||
// A class declared in a block is collected like a class expression.
|
||||
class Once extends Readable {
|
||||
_read() {
|
||||
this.push(null);
|
||||
}
|
||||
}
|
||||
function onReadable() {
|
||||
console.log("block", stream.read() === null);
|
||||
}
|
||||
const stream = new Once();
|
||||
stream.once("readable", onReadable);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// A class without a constructor that extends a stream class inherits the
|
||||
// runtime constructor, which takes no arguments here. Passing arguments
|
||||
// through the class's value reports at the first argument, as direct
|
||||
// construction does.
|
||||
import { Transform } from "node:stream";
|
||||
|
||||
class Upper extends Transform {}
|
||||
const registry = { upper: Upper };
|
||||
const upper = new registry.upper({ objectMode: true });
|
||||
upper.destroy();
|
||||
|
||||
const Kind = Upper;
|
||||
const other = new Kind({ highWaterMark: 4 });
|
||||
other.destroy();
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Writable } from "node:stream";
|
||||
|
||||
// Node passes write and end callbacks an error argument (null on success);
|
||||
// the native callbacks receive none, so a callback that names it refuses.
|
||||
const sink = new Writable({
|
||||
write(_chunk, _encoding, done) {
|
||||
done();
|
||||
},
|
||||
});
|
||||
sink.write("a", (err) => console.log("written", err));
|
||||
sink.end((err?: Error | null) => console.log("ended", err));
|
||||
@@ -0,0 +1,17 @@
|
||||
builtin-base-class-value-arguments.ts:9:34 - error SC2020: 'new Upper with arguments through an inherited stream constructor' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
8 | const registry = { upper: Upper };
|
||||
9 | const upper = new registry.upper({ objectMode: true });
|
||||
| ^~~~~~~~~~~~~~~~~~~~
|
||||
10 | upper.destroy();
|
||||
|
||||
hint: declare a constructor that passes an inline options object to super(...)
|
||||
|
||||
builtin-base-class-value-arguments.ts:13:24 - error SC2020: 'new Upper with arguments through an inherited stream constructor' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
12 | const Kind = Upper;
|
||||
13 | const other = new Kind({ highWaterMark: 4 });
|
||||
| ^~~~~~~~~~~~~~~~~~~~
|
||||
14 | other.destroy();
|
||||
|
||||
hint: declare a constructor that passes an inline options object to super(...)
|
||||
@@ -0,0 +1,17 @@
|
||||
stream-completion-callback-error.ts:10:17 - error SC2020: 'write completion callbacks taking the error argument' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
9 | });
|
||||
10 | sink.write("a", (err) => console.log("written", err));
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
11 | sink.end((err?: Error | null) => console.log("ended", err));
|
||||
|
||||
hint: () => void callbacks are supported (listen for 'error' to observe failures)
|
||||
|
||||
stream-completion-callback-error.ts:11:10 - error SC2020: 'end callbacks with parameters' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
10 | sink.write("a", (err) => console.log("written", err));
|
||||
11 | sink.end((err?: Error | null) => console.log("ended", err));
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
12 |
|
||||
|
||||
hint: () => void callbacks are supported
|
||||
@@ -359,6 +359,37 @@ const arr = tuple.map(() => new Date(0));`,
|
||||
expect(r).toEqual({ stdout: reference.stdout, stderr: reference.stderr, exitCode: 0 });
|
||||
});
|
||||
|
||||
test.each([
|
||||
["an argument", "make(Upper, { objectMode: true });"],
|
||||
["a spread argument", "spread(Upper, [{ objectMode: true }]);"],
|
||||
])(
|
||||
"%s through an untyped factory to an inherited stream constructor is a named deferred fence (JS lane)",
|
||||
async (_, call) => {
|
||||
// The constructor a ctor-less stream subclass inherits takes no
|
||||
// arguments here; typed construction refuses them at compile time,
|
||||
// and construction from untyped code refuses them at run time
|
||||
// instead of constructing without them.
|
||||
const r = await compileAndRun(
|
||||
`inherited-stream-ctor-${call.startsWith("make") ? "args" : "spread"}`,
|
||||
`'use strict';
|
||||
const { Transform } = require('node:stream');
|
||||
class Upper extends Transform {}
|
||||
function make(F, options) { return new F(options); }
|
||||
function bare(F) { return new F(); }
|
||||
function spread(F, args) { return new F(...args); }
|
||||
console.log('bare', bare(Upper) instanceof Upper, spread(Upper, []) instanceof Upper);
|
||||
${call}
|
||||
`,
|
||||
"cjs",
|
||||
);
|
||||
expect(r.exitCode).toBe(1);
|
||||
expect(r.stdout).toBe("bare true true\n");
|
||||
expect(r.stderr).toMatch(
|
||||
/^Uncaught Error: new Upper with arguments through an inherited stream constructor .* is not supported yet \[SC1090 at .*inherited-stream-ctor-\w+\.cjs:3\]\n$/,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("extending a property-assigned class ABOVE its assignment is a named deferred fence (JS lane)", async () => {
|
||||
// The property-assigned class-expression family (corpus 2032/2033
|
||||
// pins what lowers): `exports.O = class extends exports.I {}` with
|
||||
|
||||
@@ -204,14 +204,11 @@ r.on("data", (b: Buffer) => console.log(b.toString()));
|
||||
expect(llvm).toContain("call void @scr_stream_init_readable(ptr ");
|
||||
});
|
||||
|
||||
test("a runtime-fenced nested stream class emits the refusal without class helpers", async () => {
|
||||
// The phase-1 reachable bug: a class declared inside a block (a
|
||||
// runtime fence in JS) whose instances are captured emitted capture-
|
||||
// box RC adapters for the uncollected class — a C compile error.
|
||||
// run()'s unregistered-class sweep now rewrites such type slots to
|
||||
// the inert f64 placeholder before emission (no instance can exist;
|
||||
// every use traps), so the capture box is a PLAIN-kind box and the
|
||||
// class never reaches the emitter at all.
|
||||
test("a block-scoped stream class lowers natively with its class helpers", async () => {
|
||||
// A class declared inside a block is collected like a class expression.
|
||||
// Its captured instances once needed the unregistered-class sweep (the
|
||||
// class was fenced); a builtin stream base no longer fences, so the
|
||||
// class reaches the emitter with its object type and stream init.
|
||||
const llvm = await emittedLlvm(
|
||||
"stream-nested-fence",
|
||||
`const { Readable } = require("stream");
|
||||
@@ -226,11 +223,10 @@ r.on("data", (b: Buffer) => console.log(b.toString()));
|
||||
`,
|
||||
"js",
|
||||
);
|
||||
expect(llvm).not.toContain("sc_retain_R_v");
|
||||
expect(llvm).not.toContain("uncollected class");
|
||||
expect(llvm).not.toContain("%sc_o_R");
|
||||
expect(llvm).toMatch(/%sc_l_stream_0 = alloca ptr/);
|
||||
expect(llvm).toContain("call void @scr_throw_error_msg_code");
|
||||
expect(llvm).toContain("%sc_o_R");
|
||||
expect(llvm).toContain("call void @scr_stream_init_readable(ptr ");
|
||||
expect(llvm).not.toContain("call void @scr_throw_error_msg_code");
|
||||
});
|
||||
|
||||
test("a getter-only override of a pair fills the set slot with a thrower", async () => {
|
||||
|
||||
@@ -104,7 +104,7 @@ const KNOWN: Record<string, Known> = {
|
||||
"package-imports": { build: "SC2013" },
|
||||
"promise-bridge": { build: "SC1090" },
|
||||
"self-name": { build: "SC2013" },
|
||||
"stream-shims": { build: "SC1090" },
|
||||
"stream-shims": { run: "SC1090" },
|
||||
"util-shims": { run: "SC2020" },
|
||||
"web-streams": { build: "SC2013" },
|
||||
"workspace-copied": { build: "SC2013" },
|
||||
|
||||
Reference in New Issue
Block a user