@scriptc/compiler 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/ambient/package.json +3 -0
- package/ambient/scriptc-node-fallback.d.ts +3001 -0
- package/ambient/scriptc-overrides.d.ts +95 -0
- package/ambient/scriptc.d.ts +74 -0
- package/dist/backend/cc.d.ts +169 -0
- package/dist/backend/cc.js +897 -0
- package/dist/backend/cc.js.map +1 -0
- package/dist/backend/emission/emit-async.d.ts +145 -0
- package/dist/backend/emission/emit-async.js +996 -0
- package/dist/backend/emission/emit-async.js.map +1 -0
- package/dist/backend/emission/emit-exprs.d.ts +3 -0
- package/dist/backend/emission/emit-exprs.js +5949 -0
- package/dist/backend/emission/emit-exprs.js.map +1 -0
- package/dist/backend/emission/emit-island.d.ts +45 -0
- package/dist/backend/emission/emit-island.js +271 -0
- package/dist/backend/emission/emit-island.js.map +1 -0
- package/dist/backend/emission/emit-shapes.d.ts +142 -0
- package/dist/backend/emission/emit-shapes.js +575 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -0
- package/dist/backend/emission/emit-stmts.d.ts +78 -0
- package/dist/backend/emission/emit-stmts.js +960 -0
- package/dist/backend/emission/emit-stmts.js.map +1 -0
- package/dist/backend/emission/emit-types.d.ts +65 -0
- package/dist/backend/emission/emit-types.js +652 -0
- package/dist/backend/emission/emit-types.js.map +1 -0
- package/dist/backend/emission/emit-walkers.d.ts +154 -0
- package/dist/backend/emission/emit-walkers.js +1587 -0
- package/dist/backend/emission/emit-walkers.js.map +1 -0
- package/dist/backend/emission/emitter.d.ts +452 -0
- package/dist/backend/emission/emitter.js +1249 -0
- package/dist/backend/emission/emitter.js.map +1 -0
- package/dist/backend/emission/may-throw.d.ts +15 -0
- package/dist/backend/emission/may-throw.js +250 -0
- package/dist/backend/emission/may-throw.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +21 -0
- package/dist/backend/llvm/blocks.js +68 -0
- package/dist/backend/llvm/blocks.js.map +1 -0
- package/dist/backend/llvm/classes.d.ts +84 -0
- package/dist/backend/llvm/classes.js +438 -0
- package/dist/backend/llvm/classes.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +123 -0
- package/dist/backend/llvm/dyn.js +2624 -0
- package/dist/backend/llvm/dyn.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +10102 -0
- package/dist/backend/llvm/emitter.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +78 -0
- package/dist/backend/llvm/shapes.js +754 -0
- package/dist/backend/llvm/shapes.js.map +1 -0
- package/dist/backend/llvm/unsupported.d.ts +6 -0
- package/dist/backend/llvm/unsupported.js +12 -0
- package/dist/backend/llvm/unsupported.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +58 -0
- package/dist/backend/llvm/walkers.js +793 -0
- package/dist/backend/llvm/walkers.js.map +1 -0
- package/dist/backend/mangle.d.ts +124 -0
- package/dist/backend/mangle.js +232 -0
- package/dist/backend/mangle.js.map +1 -0
- package/dist/coverage/report.d.ts +65 -0
- package/dist/coverage/report.js +238 -0
- package/dist/coverage/report.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +140 -0
- package/dist/diagnostics/diagnostic.js +458 -0
- package/dist/diagnostics/diagnostic.js.map +1 -0
- package/dist/diagnostics/render.d.ts +11 -0
- package/dist/diagnostics/render.js +58 -0
- package/dist/diagnostics/render.js.map +1 -0
- package/dist/frontend/cjs-lexer.d.ts +20 -0
- package/dist/frontend/cjs-lexer.js +813 -0
- package/dist/frontend/cjs-lexer.js.map +1 -0
- package/dist/frontend/lowering/http2-constants.d.ts +1 -0
- package/dist/frontend/lowering/http2-constants.js +251 -0
- package/dist/frontend/lowering/http2-constants.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
- package/dist/frontend/lowering/lib-boundary.js +143 -0
- package/dist/frontend/lowering/lib-boundary.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.d.ts +18 -0
- package/dist/frontend/lowering/lower-assert.js +1269 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
- package/dist/frontend/lowering/lower-builtins.js +5249 -0
- package/dist/frontend/lowering/lower-builtins.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.d.ts +581 -0
- package/dist/frontend/lowering/lower-calls.js +6286 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -0
- package/dist/frontend/lowering/lower-classes.d.ts +727 -0
- package/dist/frontend/lowering/lower-classes.js +4701 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -0
- package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
- package/dist/frontend/lowering/lower-comptime.js +244 -0
- package/dist/frontend/lowering/lower-comptime.js.map +1 -0
- package/dist/frontend/lowering/lower-containers.d.ts +486 -0
- package/dist/frontend/lowering/lower-containers.js +6345 -0
- package/dist/frontend/lowering/lower-containers.js.map +1 -0
- package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
- package/dist/frontend/lowering/lower-dgram.js +319 -0
- package/dist/frontend/lowering/lower-dgram.js.map +1 -0
- package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
- package/dist/frontend/lowering/lower-emitter.js +688 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -0
- package/dist/frontend/lowering/lower-enums.d.ts +22 -0
- package/dist/frontend/lowering/lower-enums.js +235 -0
- package/dist/frontend/lowering/lower-enums.js.map +1 -0
- package/dist/frontend/lowering/lower-expando.d.ts +35 -0
- package/dist/frontend/lowering/lower-expando.js +276 -0
- package/dist/frontend/lowering/lower-expando.js.map +1 -0
- package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
- package/dist/frontend/lowering/lower-exprs.js +8111 -0
- package/dist/frontend/lowering/lower-exprs.js.map +1 -0
- package/dist/frontend/lowering/lower-generators.d.ts +50 -0
- package/dist/frontend/lowering/lower-generators.js +421 -0
- package/dist/frontend/lowering/lower-generators.js.map +1 -0
- package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
- package/dist/frontend/lowering/lower-inspect.js +1074 -0
- package/dist/frontend/lowering/lower-inspect.js.map +1 -0
- package/dist/frontend/lowering/lower-island.d.ts +108 -0
- package/dist/frontend/lowering/lower-island.js +650 -0
- package/dist/frontend/lowering/lower-island.js.map +1 -0
- package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
- package/dist/frontend/lowering/lower-mixins.js +534 -0
- package/dist/frontend/lowering/lower-mixins.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +135 -0
- package/dist/frontend/lowering/lower-modules.js +1613 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -0
- package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
- package/dist/frontend/lowering/lower-namespaces.js +706 -0
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
- package/dist/frontend/lowering/lower-server.d.ts +90 -0
- package/dist/frontend/lowering/lower-server.js +3471 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
- package/dist/frontend/lowering/lower-stmts.js +5341 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.d.ts +88 -0
- package/dist/frontend/lowering/lower-stream.js +1533 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -0
- package/dist/frontend/lowering/lower-test.d.ts +27 -0
- package/dist/frontend/lowering/lower-test.js +353 -0
- package/dist/frontend/lowering/lower-test.js.map +1 -0
- package/dist/frontend/lowering/lowerer.d.ts +1748 -0
- package/dist/frontend/lowering/lowerer.js +6257 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +272 -0
- package/dist/frontend/lowering/surfaces.js +1096 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +55 -0
- package/dist/frontend/npm-static.js +300 -0
- package/dist/frontend/npm-static.js.map +1 -0
- package/dist/frontend/npm.d.ts +330 -0
- package/dist/frontend/npm.js +1259 -0
- package/dist/frontend/npm.js.map +1 -0
- package/dist/frontend/program.d.ts +186 -0
- package/dist/frontend/program.js +2255 -0
- package/dist/frontend/program.js.map +1 -0
- package/dist/frontend/provenance-registry.d.ts +48 -0
- package/dist/frontend/provenance-registry.js +87 -0
- package/dist/frontend/provenance-registry.js.map +1 -0
- package/dist/frontend/provenance.d.ts +6 -0
- package/dist/frontend/provenance.js +459 -0
- package/dist/frontend/provenance.js.map +1 -0
- package/dist/frontend/resolve.d.ts +56 -0
- package/dist/frontend/resolve.js +682 -0
- package/dist/frontend/resolve.js.map +1 -0
- package/dist/frontend/shared.d.ts +67 -0
- package/dist/frontend/shared.js +241 -0
- package/dist/frontend/shared.js.map +1 -0
- package/dist/frontend/ts7/adapter.d.ts +7 -0
- package/dist/frontend/ts7/adapter.js +54 -0
- package/dist/frontend/ts7/adapter.js.map +1 -0
- package/dist/frontend/ts7/ast.d.ts +50 -0
- package/dist/frontend/ts7/ast.js +211 -0
- package/dist/frontend/ts7/ast.js.map +1 -0
- package/dist/frontend/ts7/census-check.d.ts +1 -0
- package/dist/frontend/ts7/census-check.js +12 -0
- package/dist/frontend/ts7/census-check.js.map +1 -0
- package/dist/frontend/ts7/checker.d.ts +140 -0
- package/dist/frontend/ts7/checker.js +544 -0
- package/dist/frontend/ts7/checker.js.map +1 -0
- package/dist/frontend/ts7/enums.d.ts +18 -0
- package/dist/frontend/ts7/enums.js +43 -0
- package/dist/frontend/ts7/enums.js.map +1 -0
- package/dist/frontend/ts7/program.d.ts +99 -0
- package/dist/frontend/ts7/program.js +278 -0
- package/dist/frontend/ts7/program.js.map +1 -0
- package/dist/frontend/ts7/world-check.d.ts +3 -0
- package/dist/frontend/ts7/world-check.js +34 -0
- package/dist/frontend/ts7/world-check.js.map +1 -0
- package/dist/frontend/types.d.ts +205 -0
- package/dist/frontend/types.js +2485 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +429 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/nodes.d.ts +4334 -0
- package/dist/ir/nodes.js +1906 -0
- package/dist/ir/nodes.js.map +1 -0
- package/dist/ir/serialize.d.ts +4 -0
- package/dist/ir/serialize.js +34 -0
- package/dist/ir/serialize.js.map +1 -0
- package/dist/ir/validate.d.ts +36 -0
- package/dist/ir/validate.js +4885 -0
- package/dist/ir/validate.js.map +1 -0
- package/package.json +30 -6
- package/README.md +0 -3
- package/index.js +0 -2
|
@@ -0,0 +1,1533 @@
|
|
|
1
|
+
/* The node:stream lowering (the spoke-module pattern, like
|
|
2
|
+
* lower-emitter.ts): construction of the five runtime stream classes
|
|
3
|
+
* (`new Readable({ read() {} })` et al — the OPTIONS-OBJECT form), user
|
|
4
|
+
* subclasses (`class My extends Readable { _read() {...} }` — the
|
|
5
|
+
* underscore-override form: super(options) lowers to a stream.init
|
|
6
|
+
* libCall over the already-allocated subclass struct, and overridden
|
|
7
|
+
* underscore methods bind as compiler-synthesized wrapper closures that
|
|
8
|
+
* dispatch through the vtable, so a further-derived override wins even
|
|
9
|
+
* under an inherited constructor), the stream method surface
|
|
10
|
+
* (push/read/pause/resume/pipe/write/end/destroy/...) on stream-rooted
|
|
11
|
+
* receivers, the readableEnded-family property reads, and the per-base
|
|
12
|
+
* FORCED event tuples the emitter spoke consults ('data' is one Buffer,
|
|
13
|
+
* 'pipe'/'unpipe' one Readable, the lifecycle events empty — runtime-
|
|
14
|
+
* emitted payloads must be typed without colliding with user events of
|
|
15
|
+
* the same names on plain emitters; the 'error' forced-tuple precedent,
|
|
16
|
+
* scoped per runtime base).
|
|
17
|
+
*
|
|
18
|
+
* THE TYPING STANCE. Chunks are BYTES: 'data' delivers Buffers, read()
|
|
19
|
+
* answers `Buffer | null`, and push/write accept a Buffer or a utf8
|
|
20
|
+
* string (converted at the call, Node's decodeStrings default).
|
|
21
|
+
* objectMode, setEncoding, and non-utf8 encodings have no static story
|
|
22
|
+
* yet and fence with their own words. Option callbacks (`read`, `write`,
|
|
23
|
+
* `final`, `destroy`, `transform`, `flush`) lower as closures with a
|
|
24
|
+
* LEADING `this` parameter — Node binds `this` to the stream when it
|
|
25
|
+
* calls them, so method-shorthand bodies (`read() { this.push(...) }`)
|
|
26
|
+
* resolve `this` to that parameter; the runtime passes the stream through
|
|
27
|
+
* compiler-emitted invoke adapters. A callback may declare any PREFIX of
|
|
28
|
+
* its Node signature (the emitter listener rule). */
|
|
29
|
+
import * as ts from "../ts7/adapter.js";
|
|
30
|
+
import { dynFallbackType } from "./lowerer.js";
|
|
31
|
+
import { locOf } from "../program.js";
|
|
32
|
+
import { newFnCtx, own } from "./lowerer.js";
|
|
33
|
+
import { appendImplicitUndefinedReturn } from "./lower-calls.js";
|
|
34
|
+
import { bufEncoding } from "./lower-containers.js";
|
|
35
|
+
import { probeLower } from "./lower-exprs.js";
|
|
36
|
+
import { BOOL, DYN, F64, RUNTIME_STREAM_CLASSES, STRING, VOID, arrayOf, bytesOf, canBoxFuncIntoDyn, funcOf, typeEquals, typeKey } from "../../ir/nodes.js";
|
|
37
|
+
const BYTES = bytesOf("u8");
|
|
38
|
+
/** The stream sides of a receiver class: the nearest stream-class
|
|
39
|
+
* ancestor's, or null off the stream hierarchy. */
|
|
40
|
+
export function streamSidesOf(L, info) {
|
|
41
|
+
for (let c = info ?? null; c; c = c.base) {
|
|
42
|
+
if (c.builtinStream)
|
|
43
|
+
return c.builtinStream;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/** The instance members the stream spoke owns on stream-rooted
|
|
48
|
+
* receivers (the emitter members ride the base chain as usual). */
|
|
49
|
+
export const STREAM_API_MEMBERS = new Set([
|
|
50
|
+
"push", "unshift", "read", "pause", "resume", "isPaused", "setEncoding",
|
|
51
|
+
"pipe", "unpipe", "destroy", "write", "end", "cork", "uncork",
|
|
52
|
+
"setDefaultEncoding",
|
|
53
|
+
]);
|
|
54
|
+
/** The property names the stream spoke owns (lowerStreamProperty) — a
|
|
55
|
+
* subclass field or accessor with one of these names would split-brain
|
|
56
|
+
* against the runtime state, so collection fences them (the
|
|
57
|
+
* EMITTER_API_MEMBERS precedent). */
|
|
58
|
+
export const STREAM_PROP_MEMBERS = new Set([
|
|
59
|
+
"readable", "readableEnded", "readableFlowing", "readableLength",
|
|
60
|
+
"readableHighWaterMark", "readableObjectMode",
|
|
61
|
+
"writable", "writableEnded", "writableFinished", "writableNeedDrain",
|
|
62
|
+
"writableLength", "writableHighWaterMark", "writableCorked",
|
|
63
|
+
"writableObjectMode", "destroyed", "closed", "errored", "allowHalfOpen",
|
|
64
|
+
]);
|
|
65
|
+
/** The underscore methods each runtime base consumes at construction
|
|
66
|
+
* (option-callback name → method name). A stream subclass declaring an
|
|
67
|
+
* underscore method OUTSIDE its base's set (a `_read` on a Transform,
|
|
68
|
+
* `_writev`/`_construct` anywhere) is fenced at collection: Node would
|
|
69
|
+
* consume it through machinery that has no lowering here. */
|
|
70
|
+
export const UNDERSCORE_METHODS = new Map([
|
|
71
|
+
["read", "_read"],
|
|
72
|
+
["write", "_write"],
|
|
73
|
+
["final", "_final"],
|
|
74
|
+
["destroy", "_destroy"],
|
|
75
|
+
["transform", "_transform"],
|
|
76
|
+
["flush", "_flush"],
|
|
77
|
+
]);
|
|
78
|
+
/** The accepted option-callback names (canonical flag order) and the
|
|
79
|
+
* duplex-shaped-head answer for one runtime stream class. */
|
|
80
|
+
export function streamCtorShape(cls) {
|
|
81
|
+
switch (cls) {
|
|
82
|
+
case "%Readable":
|
|
83
|
+
return { fn: "readable", accepted: ["read", "destroy"], duplexShape: false };
|
|
84
|
+
case "%Writable":
|
|
85
|
+
return { fn: "writable", accepted: ["write", "final", "destroy"], duplexShape: false };
|
|
86
|
+
case "%Duplex":
|
|
87
|
+
return { fn: "duplex", accepted: ["read", "write", "final", "destroy"], duplexShape: true };
|
|
88
|
+
case "%Transform":
|
|
89
|
+
return { fn: "transform", accepted: ["transform", "flush", "destroy"], duplexShape: true };
|
|
90
|
+
case "%PassThrough":
|
|
91
|
+
return { fn: "passthrough", accepted: ["transform", "flush", "destroy"], duplexShape: true };
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`lowerer bug: streamCtorShape of ${cls}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** The per-base FORCED event tuples (runtime-emitted payloads): consulted
|
|
97
|
+
* by the emitter spoke BEFORE the program-global table, so a stream's
|
|
98
|
+
* 'data' never collides with a user event named 'data' on a plain
|
|
99
|
+
* emitter. 'error' stays globally forced to the one-%Error tuple. */
|
|
100
|
+
export function streamForcedTuple(L, info, name) {
|
|
101
|
+
const sides = streamSidesOf(L, info);
|
|
102
|
+
if (!sides)
|
|
103
|
+
return null;
|
|
104
|
+
const readable = {
|
|
105
|
+
data: [BYTES],
|
|
106
|
+
end: [],
|
|
107
|
+
close: [],
|
|
108
|
+
readable: [],
|
|
109
|
+
pause: [],
|
|
110
|
+
resume: [],
|
|
111
|
+
};
|
|
112
|
+
const writable = {
|
|
113
|
+
drain: [],
|
|
114
|
+
finish: [],
|
|
115
|
+
close: [],
|
|
116
|
+
pipe: [{ kind: "object", className: "%Readable" }],
|
|
117
|
+
unpipe: [{ kind: "object", className: "%Readable" }],
|
|
118
|
+
};
|
|
119
|
+
// own(), not `in`: `name` is a USER-written event name, and
|
|
120
|
+
// `"toString" in readable` answers true through Object.prototype.
|
|
121
|
+
if (sides === "r" || sides === "rw") {
|
|
122
|
+
const tuple = own(readable, name);
|
|
123
|
+
if (tuple)
|
|
124
|
+
return tuple;
|
|
125
|
+
}
|
|
126
|
+
if (sides === "w" || sides === "rw") {
|
|
127
|
+
const tuple = own(writable, name);
|
|
128
|
+
if (tuple)
|
|
129
|
+
return tuple;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const strLit = (value, loc) => ({ kind: "strLit", value, type: STRING, loc });
|
|
134
|
+
const boolLit = (value, loc) => ({ kind: "boolLit", value, type: BOOL, loc });
|
|
135
|
+
const f64Lit = (value, loc) => ({ kind: "numLit", value, type: F64, loc });
|
|
136
|
+
/* ── option callbacks (the leading-`this` closures) ─────────────────── */
|
|
137
|
+
/** Lowers one option callback (method shorthand, function expression, or
|
|
138
|
+
* arrow) into a closure whose lifted function takes the STREAM as a
|
|
139
|
+
* leading `this` parameter. Method/function bodies bind `this` to it
|
|
140
|
+
* (Node calls them stream-bound); arrows keep lexical `this` (the hidden
|
|
141
|
+
* leading param is ABI-only). The declared parameters must be a
|
|
142
|
+
* typeEquals PREFIX of the callback's Node signature. */
|
|
143
|
+
function lowerStreamCallback(L, which, node, thisType, fullTuple) {
|
|
144
|
+
const loc = locOf(node);
|
|
145
|
+
const { shapes, funcType } = L.lambdaSignature(node);
|
|
146
|
+
if (node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) {
|
|
147
|
+
L.unsupported("SC1090", node, `async stream '${which}' callbacks`);
|
|
148
|
+
}
|
|
149
|
+
if (funcType.params.length > fullTuple.length) {
|
|
150
|
+
L.noLowering(`a '${which}' callback declaring ${funcType.params.length} parameters where Node passes ${fullTuple.length}`, node, "stream option callbacks may declare a prefix of their Node signature");
|
|
151
|
+
}
|
|
152
|
+
for (let i = 0; i < funcType.params.length; i++) {
|
|
153
|
+
// A checked-dynamic parameter (the JS lane's implicitly-any
|
|
154
|
+
// callbacks) takes whatever Node passes — the emitted invoke thunk
|
|
155
|
+
// boxes each position by kind (the completion callback arrives as a
|
|
156
|
+
// callable dyn whose glue dispatches to the runtime's *_done).
|
|
157
|
+
if (funcType.params[i].kind === "dyn")
|
|
158
|
+
continue;
|
|
159
|
+
if (!typeEquals(funcType.params[i], fullTuple[i])) {
|
|
160
|
+
L.noLowering(`a '${which}' callback whose parameter ${i} is '${L.fmt(funcType.params[i])}' where Node passes '${L.fmt(fullTuple[i])}'`, node, "annotate the parameter with the Node signature's type (chunks are Buffers here)");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const fnName = `%fn${L.lambdaCounter++}_${which}cb`;
|
|
164
|
+
const fnCtx = newFnCtx(true, null, funcType, funcType.ret);
|
|
165
|
+
L.fnStack.push(fnCtx);
|
|
166
|
+
try {
|
|
167
|
+
// Method shorthand and function expressions see the stream as `this`
|
|
168
|
+
// (Node binds it); arrows keep lexical `this`, so their slot is a
|
|
169
|
+
// hidden ABI local no body reference can resolve to.
|
|
170
|
+
const thisLocal = ts.isArrowFunction(node)
|
|
171
|
+
? L.declareHiddenLocal("this", thisType)
|
|
172
|
+
: L.declareThis(thisType);
|
|
173
|
+
const { params, prologue } = L.declareParams(node.parameters, shapes);
|
|
174
|
+
let body;
|
|
175
|
+
if (node.body !== undefined && ts.isBlock(node.body)) {
|
|
176
|
+
body = L.lowerStmts(node.body.statements);
|
|
177
|
+
}
|
|
178
|
+
else if (node.body !== undefined) {
|
|
179
|
+
// Bare-expression bodies (`read: () => this.push(null)`): the value
|
|
180
|
+
// is discarded when the signature returns void — Node ignores
|
|
181
|
+
// option-callback results either way.
|
|
182
|
+
const bodyExpr = node.body;
|
|
183
|
+
if (funcType.ret.kind === "void") {
|
|
184
|
+
body = [{ kind: "exprStmt", expr: L.lowerExpr(bodyExpr), loc: locOf(node.body) }];
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const value = L.lowerExpr(bodyExpr);
|
|
188
|
+
body = [{ kind: "return", value: L.coerceInto(bodyExpr, value, funcType.ret), loc: locOf(node.body) }];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
body = [];
|
|
193
|
+
}
|
|
194
|
+
body = [...prologue, ...body];
|
|
195
|
+
appendImplicitUndefinedReturn(L, body, funcType.ret, loc);
|
|
196
|
+
const ctx = L.ctx;
|
|
197
|
+
const lifted = {
|
|
198
|
+
name: fnName,
|
|
199
|
+
params: [{ localId: thisLocal.id, name: "this", type: thisType }, ...params],
|
|
200
|
+
returnType: funcType.ret,
|
|
201
|
+
locals: ctx.locals,
|
|
202
|
+
captures: ctx.captures,
|
|
203
|
+
body,
|
|
204
|
+
loc,
|
|
205
|
+
};
|
|
206
|
+
L.liftedFns.push(lifted);
|
|
207
|
+
return {
|
|
208
|
+
kind: "closure",
|
|
209
|
+
fnName,
|
|
210
|
+
captures: ctx.captureSources,
|
|
211
|
+
type: funcOf([thisType, ...funcType.params], funcType.ret),
|
|
212
|
+
loc,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
L.fnStack.pop();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** The full Node parameter tuples of the option callbacks, given the
|
|
220
|
+
* concrete stream's `this` type. The trailing completion callbacks keep
|
|
221
|
+
* whatever func type the checker contextually gave the user's parameter
|
|
222
|
+
* (the emitted adapter constructs a matching closure), so only their
|
|
223
|
+
* FUNC-ness is checked here. */
|
|
224
|
+
function cbTuples(L) {
|
|
225
|
+
return {
|
|
226
|
+
read: [F64],
|
|
227
|
+
write: [BYTES, STRING, "fn"],
|
|
228
|
+
final: ["fn"],
|
|
229
|
+
destroy: [errorOrNull(L), "fn"],
|
|
230
|
+
transform: [BYTES, STRING, "fn"],
|
|
231
|
+
flush: ["fn"],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/** Interns a union from unsorted arms (the interner's identity is the
|
|
235
|
+
* typeKey-SORTED arm list — mapType's canonicalization). */
|
|
236
|
+
function unionOf(L, arms) {
|
|
237
|
+
const sorted = [...arms].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
238
|
+
return { kind: "union", unionId: L.unions.intern(sorted) };
|
|
239
|
+
}
|
|
240
|
+
function errorOrNull(L) {
|
|
241
|
+
return unionOf(L, [{ kind: "object", className: "%Error" }, { kind: "nullT" }]);
|
|
242
|
+
}
|
|
243
|
+
/** The program's property-mutated symbols (`opts.read = f`, `opts["x"]
|
|
244
|
+
* op= v`, `delete opts.y`): a const-initialized options record whose
|
|
245
|
+
* symbol appears here cannot be followed to its literal — the runtime
|
|
246
|
+
* record and the compile-time walk would split-brain. Built once, lazily
|
|
247
|
+
* (the emitterEvents pre-pass precedent); the scan is diagnostic-free. */
|
|
248
|
+
function propMutatedSymbols(L) {
|
|
249
|
+
const holder = L;
|
|
250
|
+
if (holder.streamPropMutatedSyms)
|
|
251
|
+
return holder.streamPropMutatedSyms;
|
|
252
|
+
const set = new Set();
|
|
253
|
+
const noteBase = (target) => {
|
|
254
|
+
let base = target;
|
|
255
|
+
while (ts.isPropertyAccessExpression(base) || ts.isElementAccessExpression(base)) {
|
|
256
|
+
base = base.expression;
|
|
257
|
+
}
|
|
258
|
+
if (!ts.isIdentifier(base))
|
|
259
|
+
return;
|
|
260
|
+
try {
|
|
261
|
+
const sym = L.resolveValueSymbol(base);
|
|
262
|
+
if (sym)
|
|
263
|
+
set.add(sym);
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
/* not a candidate */
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
for (const sf of L.program.getSourceFiles()) {
|
|
270
|
+
if (sf.isDeclarationFile)
|
|
271
|
+
continue;
|
|
272
|
+
const walk = (node) => {
|
|
273
|
+
ts.forEachChild(node, walk);
|
|
274
|
+
if (ts.isBinaryExpression(node)
|
|
275
|
+
&& node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment
|
|
276
|
+
&& node.operatorToken.kind <= ts.SyntaxKind.LastAssignment
|
|
277
|
+
&& (ts.isPropertyAccessExpression(node.left) || ts.isElementAccessExpression(node.left))) {
|
|
278
|
+
noteBase(node.left);
|
|
279
|
+
}
|
|
280
|
+
else if (ts.isDeleteExpression(node)) {
|
|
281
|
+
noteBase(node.expression);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
walk(sf);
|
|
285
|
+
}
|
|
286
|
+
holder.streamPropMutatedSyms = set;
|
|
287
|
+
return set;
|
|
288
|
+
}
|
|
289
|
+
/** Follows a non-literal options argument to its compile-time object
|
|
290
|
+
* literal: a chain of const-declared, initialized variables ending at an
|
|
291
|
+
* object literal (`const opts = { read() {} }; new Readable(opts)`).
|
|
292
|
+
* Lowering the SAME literal at every construction site is sound for
|
|
293
|
+
* scoping — the declaration's scope is an ancestor of every use site, so
|
|
294
|
+
* each identifier in a callback body resolves to the identical binding
|
|
295
|
+
* and captures it the ordinary way — and sound for identity because a
|
|
296
|
+
* followed symbol is checked against the program's property mutations
|
|
297
|
+
* (propMutatedSymbols). Returns the argument unchanged when the chain
|
|
298
|
+
* breaks; `undefined`/null arguments answer null (Node's `if (options)`
|
|
299
|
+
* guard — all defaults). */
|
|
300
|
+
function followOptionsArg(L, arg) {
|
|
301
|
+
let node = arg;
|
|
302
|
+
for (let hops = 0; node !== undefined && hops < 16; hops++) {
|
|
303
|
+
if (ts.isObjectLiteralExpression(node))
|
|
304
|
+
return node;
|
|
305
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
306
|
+
node = node.expression;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (node.kind === ts.SyntaxKind.NullKeyword)
|
|
310
|
+
return null;
|
|
311
|
+
if (!ts.isIdentifier(node))
|
|
312
|
+
return node;
|
|
313
|
+
if (node.text === "undefined")
|
|
314
|
+
return null;
|
|
315
|
+
let sym;
|
|
316
|
+
try {
|
|
317
|
+
sym = L.resolveValueSymbol(node);
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return node;
|
|
321
|
+
}
|
|
322
|
+
const decl = sym ? L.checker.valueDeclarationOf(sym) : undefined;
|
|
323
|
+
if (!sym || !decl || !ts.isVariableDeclaration(decl) || decl.initializer === undefined)
|
|
324
|
+
return node;
|
|
325
|
+
if ((ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) === 0)
|
|
326
|
+
return node;
|
|
327
|
+
if (propMutatedSymbols(L).has(sym))
|
|
328
|
+
return node;
|
|
329
|
+
node = decl.initializer;
|
|
330
|
+
}
|
|
331
|
+
return node;
|
|
332
|
+
}
|
|
333
|
+
/** A callback slot holding a VALUE instead of an inline function (`read:
|
|
334
|
+
* common.mustCall(fn)`, `{ write: myWrite }`): the value adapts through
|
|
335
|
+
* the checked-dynamic function boundary exactly like dyn listeners —
|
|
336
|
+
* boxed to dyn (statically-typed functions via dynFrom, checked-dynamic
|
|
337
|
+
* values as they are), then dynCheck'd into the all-dyn full Node tuple.
|
|
338
|
+
* The emitted option thunk recognizes the THISLESS shape (its first
|
|
339
|
+
* param is not the stream) and boxes each position by kind — the chunk
|
|
340
|
+
* as DOM bytes, the error via the boundary's encoding, the completion
|
|
341
|
+
* callback as a callable dyn whose glue reports to the runtime's *_done.
|
|
342
|
+
* DIVERGENCE (SEMANTICS.md): Node binds `this` to the stream when it
|
|
343
|
+
* calls option callbacks; a value adapted through the dyn boundary is
|
|
344
|
+
* called with `this` undefined (the dyn call ABI carries no receiver) —
|
|
345
|
+
* reference the stream through a lexical binding instead. */
|
|
346
|
+
function lowerStreamCallbackValue(L, ctorName, which, node, fullLen) {
|
|
347
|
+
const loc = locOf(node);
|
|
348
|
+
const getRecord = (id) => L.shapes.get(id);
|
|
349
|
+
const getUnion = (id) => L.unions.get(id);
|
|
350
|
+
let t;
|
|
351
|
+
try {
|
|
352
|
+
const ct = L.typeOf(node);
|
|
353
|
+
t = L.mapTypeOf(ct) ?? dynFallbackType(L, node, ct);
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
t = null;
|
|
357
|
+
}
|
|
358
|
+
let cbDyn;
|
|
359
|
+
if (t?.kind === "dyn") {
|
|
360
|
+
const v = L.lowerExpr(node);
|
|
361
|
+
cbDyn = v.type.kind === "dyn" ? v : { kind: "dynFrom", value: v, type: DYN, loc };
|
|
362
|
+
}
|
|
363
|
+
else if (t?.kind === "func") {
|
|
364
|
+
const cb = L.lowerExpr(node);
|
|
365
|
+
if (cb.type.kind !== "func" || !canBoxFuncIntoDyn(cb.type, getRecord, getUnion)) {
|
|
366
|
+
L.noLowering(`the ${ctorName} option '${which}' with a function value of this signature`, node, "a callback value's own parameters and return must cross the checked-dynamic boundary — write the callback inline to keep it fully static");
|
|
367
|
+
}
|
|
368
|
+
cbDyn = { kind: "dynFrom", value: cb, type: DYN, loc };
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
L.noLowering(`the ${ctorName} option '${which}' with a non-function value`, node, "the callback slot takes an inline function (fully static) or a function value that crosses the checked-dynamic boundary");
|
|
372
|
+
}
|
|
373
|
+
const adapterT = funcOf(Array.from({ length: fullLen }, () => DYN), VOID);
|
|
374
|
+
return { kind: "dynCheck", value: cbDyn, type: adapterT, loc };
|
|
375
|
+
}
|
|
376
|
+
/** A checked-dynamic options VALUE (the JS lane's `super(options)`
|
|
377
|
+
* forwarding a constructor parameter, `new Readable(dynVar)`): the
|
|
378
|
+
* record's shape is runtime data, so the option walk runs in the runtime
|
|
379
|
+
* (the `.newDyn`/`.initDyn` twins) with Node's own reading rules. Null
|
|
380
|
+
* when the argument is not dyn-flavored (the static walk owns it). */
|
|
381
|
+
function dynOptionsValue(L, arg) {
|
|
382
|
+
const followed = followOptionsArg(L, arg);
|
|
383
|
+
if (followed === null || followed === undefined || ts.isObjectLiteralExpression(followed))
|
|
384
|
+
return null;
|
|
385
|
+
let t;
|
|
386
|
+
try {
|
|
387
|
+
const ct = L.typeOf(followed);
|
|
388
|
+
t = L.mapTypeOf(ct) ?? dynFallbackType(L, followed, ct);
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
if (t?.kind !== "dyn")
|
|
394
|
+
return null;
|
|
395
|
+
const v = L.lowerExpr(followed);
|
|
396
|
+
return v.type.kind === "dyn" ? v : null;
|
|
397
|
+
}
|
|
398
|
+
/** Parses the options object literal of a stream constructor. `which`
|
|
399
|
+
* names the callbacks this class accepts, in canonical flag order. */
|
|
400
|
+
function parseStreamOptions(L, ctorName, arg, thisType, accepted, loc,
|
|
401
|
+
/** Subclass construction: the wrapper closure for an overridden
|
|
402
|
+
* underscore method, consulted for callbacks the options did not
|
|
403
|
+
* provide (an options callback shadows the method, Node's instance-
|
|
404
|
+
* property rule). */
|
|
405
|
+
fallbackCb) {
|
|
406
|
+
const out = {
|
|
407
|
+
hwmR: f64Lit(-1, loc),
|
|
408
|
+
hwmW: f64Lit(-1, loc),
|
|
409
|
+
autoDestroy: boolLit(true, loc),
|
|
410
|
+
emitClose: boolLit(true, loc),
|
|
411
|
+
allowHalfOpen: boolLit(true, loc),
|
|
412
|
+
readableSide: true,
|
|
413
|
+
writableSide: true,
|
|
414
|
+
encoding: null,
|
|
415
|
+
flags: 0,
|
|
416
|
+
cbs: [],
|
|
417
|
+
};
|
|
418
|
+
const followed = followOptionsArg(L, arg);
|
|
419
|
+
const optsLit = followed === null ? undefined : followed;
|
|
420
|
+
if (optsLit !== undefined && !ts.isObjectLiteralExpression(optsLit)) {
|
|
421
|
+
L.noLowering(`new ${ctorName} with a non-literal options argument`, optsLit, "the options must be an object literal, inline or through const-initialized bindings (the callbacks lower as compile-time closures)");
|
|
422
|
+
}
|
|
423
|
+
const tuples = cbTuples(L);
|
|
424
|
+
const present = new Map();
|
|
425
|
+
for (const prop of optsLit?.properties ?? []) {
|
|
426
|
+
const propName = ts.isSpreadAssignment(prop) ? undefined : prop.name;
|
|
427
|
+
const name = propName && ts.isIdentifier(propName)
|
|
428
|
+
? propName.text
|
|
429
|
+
: propName && ts.isStringLiteral(propName)
|
|
430
|
+
? propName.text
|
|
431
|
+
: null;
|
|
432
|
+
if (name === null) {
|
|
433
|
+
L.unsupported("SC1090", prop, `computed or spread members in ${ctorName} options`);
|
|
434
|
+
}
|
|
435
|
+
// Callback members: shorthand methods and function-valued properties.
|
|
436
|
+
if (accepted.includes(name)) {
|
|
437
|
+
let fnNode = null;
|
|
438
|
+
if (ts.isMethodDeclaration(prop))
|
|
439
|
+
fnNode = prop;
|
|
440
|
+
else if (ts.isPropertyAssignment(prop) &&
|
|
441
|
+
(ts.isFunctionExpression(prop.initializer) || ts.isArrowFunction(prop.initializer))) {
|
|
442
|
+
fnNode = prop.initializer;
|
|
443
|
+
}
|
|
444
|
+
if (!fnNode) {
|
|
445
|
+
// A callback VALUE (`read: common.mustCall(fn)`, `{ write:
|
|
446
|
+
// myWrite }`, shorthand `{ read }`): adapt through the checked-
|
|
447
|
+
// dynamic function boundary (the dyn-listener precedent).
|
|
448
|
+
const valueNode = ts.isPropertyAssignment(prop)
|
|
449
|
+
? prop.initializer
|
|
450
|
+
: ts.isShorthandPropertyAssignment(prop) && ts.isIdentifier(prop.name)
|
|
451
|
+
? prop.name
|
|
452
|
+
: null;
|
|
453
|
+
if (valueNode === null) {
|
|
454
|
+
L.noLowering(`the ${ctorName} option '${name}' as this member form`, prop, "write the callback inline (method shorthand or an inline function/arrow), or pass a function value");
|
|
455
|
+
}
|
|
456
|
+
present.set(name, lowerStreamCallbackValue(L, ctorName, name, valueNode, tuples[name].length));
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const full = tuples[name].map((t) => (t === "fn" ? null : t));
|
|
460
|
+
// The completion-callback tail: typed by the checker contextually;
|
|
461
|
+
// accept the user's declared func type as the tuple's entry so the
|
|
462
|
+
// prefix check compares like with like.
|
|
463
|
+
const { funcType } = L.lambdaSignature(fnNode);
|
|
464
|
+
const tuple = full.map((t, i) => {
|
|
465
|
+
if (t !== null)
|
|
466
|
+
return t;
|
|
467
|
+
const declared = funcType.params[i];
|
|
468
|
+
if (declared !== undefined && declared.kind !== "func" && declared.kind !== "dyn") {
|
|
469
|
+
L.noLowering(`a '${name}' callback whose parameter ${i} is '${L.fmt(declared)}' where Node passes the completion callback`, fnNode);
|
|
470
|
+
}
|
|
471
|
+
return declared ?? funcOf([], VOID);
|
|
472
|
+
});
|
|
473
|
+
present.set(name, lowerStreamCallback(L, name, fnNode, thisType, tuple));
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
// Scalar options.
|
|
477
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
478
|
+
L.unsupported("SC1090", prop, `the ${ctorName} option '${name}' as a shorthand/method member`);
|
|
479
|
+
}
|
|
480
|
+
const value = prop.initializer;
|
|
481
|
+
switch (name) {
|
|
482
|
+
case "highWaterMark": {
|
|
483
|
+
const v = L.lowerExprExpecting(value, F64);
|
|
484
|
+
out.hwmR = v;
|
|
485
|
+
out.hwmW = v;
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
case "readableHighWaterMark":
|
|
489
|
+
out.hwmR = L.lowerExprExpecting(value, F64);
|
|
490
|
+
break;
|
|
491
|
+
case "writableHighWaterMark":
|
|
492
|
+
out.hwmW = L.lowerExprExpecting(value, F64);
|
|
493
|
+
break;
|
|
494
|
+
case "autoDestroy":
|
|
495
|
+
out.autoDestroy = L.lowerExprExpecting(value, BOOL);
|
|
496
|
+
break;
|
|
497
|
+
case "emitClose":
|
|
498
|
+
out.emitClose = L.lowerExprExpecting(value, BOOL);
|
|
499
|
+
break;
|
|
500
|
+
case "allowHalfOpen":
|
|
501
|
+
out.allowHalfOpen = L.lowerExprExpecting(value, BOOL);
|
|
502
|
+
break;
|
|
503
|
+
case "objectMode":
|
|
504
|
+
case "readableObjectMode":
|
|
505
|
+
case "writableObjectMode": {
|
|
506
|
+
// `objectMode: false` is the byte default — admit the literal.
|
|
507
|
+
if (value.kind === ts.SyntaxKind.FalseKeyword)
|
|
508
|
+
break;
|
|
509
|
+
L.noLowering(`${ctorName} in objectMode`, prop, "object-mode streams have no static chunk type yet — chunks are Buffers (utf8 strings convert at push/write)");
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
case "decodeStrings": {
|
|
513
|
+
if (value.kind === ts.SyntaxKind.TrueKeyword)
|
|
514
|
+
break; // Node's default
|
|
515
|
+
L.noLowering(`${ctorName} with decodeStrings: false`, prop, "chunks always decode to Buffers here (Node's default)");
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
case "encoding": {
|
|
519
|
+
// The readable-side string-chunk mode: a literal encoding, folded
|
|
520
|
+
// to its canonical name (Writable has no such option).
|
|
521
|
+
if (ctorName === "Writable")
|
|
522
|
+
L.noLowering(`the Writable option 'encoding'`, prop);
|
|
523
|
+
out.encoding = bufEncoding(L, `the ${ctorName} option 'encoding'`, value);
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
case "defaultEncoding":
|
|
527
|
+
// The WRITE-side default: utf8 is the byte default — admit the
|
|
528
|
+
// literal; other encodings would change what write(string) means.
|
|
529
|
+
{
|
|
530
|
+
const t = L.typeOf(value);
|
|
531
|
+
if (t.isStringLiteralType() && (t.value === "utf8" || t.value === "utf-8"))
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
L.noLowering(`the ${ctorName} option 'defaultEncoding'`, prop, "utf8 (the default) is the supported write-side string encoding");
|
|
535
|
+
break;
|
|
536
|
+
case "signal":
|
|
537
|
+
L.noLowering(`the ${ctorName} option 'signal'`, prop, "AbortSignal-driven destruction is not lowered yet");
|
|
538
|
+
break;
|
|
539
|
+
case "captureRejections": {
|
|
540
|
+
// `false` is the default — admit the literal; the rejection-
|
|
541
|
+
// capturing mode has no lowering (async listeners' promises are
|
|
542
|
+
// abandoned unobserved).
|
|
543
|
+
if (value.kind === ts.SyntaxKind.FalseKeyword)
|
|
544
|
+
break;
|
|
545
|
+
L.noLowering(`the ${ctorName} option 'captureRejections'`, prop, "captureRejections has no lowering (listener rejections are not routed to 'error')");
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case "readable":
|
|
549
|
+
case "writable": {
|
|
550
|
+
// Duplex side toggles: compile-time literals only (they decide
|
|
551
|
+
// which halves EXIST).
|
|
552
|
+
if (ctorName !== "Duplex") {
|
|
553
|
+
L.noLowering(`the ${ctorName} option '${name}'`, prop);
|
|
554
|
+
}
|
|
555
|
+
if (value.kind === ts.SyntaxKind.TrueKeyword)
|
|
556
|
+
break;
|
|
557
|
+
if (value.kind === ts.SyntaxKind.FalseKeyword) {
|
|
558
|
+
if (name === "readable")
|
|
559
|
+
out.readableSide = false;
|
|
560
|
+
else
|
|
561
|
+
out.writableSide = false;
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
L.noLowering(`the Duplex option '${name}' with a non-literal value`, prop, "the side toggles must be compile-time booleans");
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
case "final": {
|
|
568
|
+
L.noLowering(`the ${ctorName} option 'final'`, prop, ctorName === "Transform"
|
|
569
|
+
? "a user final on a Transform replaces the _flush composition — no lowering yet"
|
|
570
|
+
: "the final callback is supported on Writable and Duplex");
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
case "construct":
|
|
574
|
+
L.noLowering(`the ${ctorName} option 'construct'`, prop, "deferred construction (_construct) is not lowered yet");
|
|
575
|
+
break;
|
|
576
|
+
case "writev":
|
|
577
|
+
L.noLowering(`the ${ctorName} option 'writev'`, prop, "batched writes (_writev) are not lowered yet — writes deliver one chunk at a time");
|
|
578
|
+
break;
|
|
579
|
+
default:
|
|
580
|
+
L.noLowering(`the ${ctorName} option '${name}'`, prop);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// Canonical flag order = the accepted list's order. Options win over
|
|
584
|
+
// overridden underscore methods (Node assigns options callbacks onto
|
|
585
|
+
// the instance, shadowing the prototype method).
|
|
586
|
+
for (let i = 0; i < accepted.length; i++) {
|
|
587
|
+
const cb = present.get(accepted[i]) ?? fallbackCb?.(accepted[i]) ?? null;
|
|
588
|
+
if (cb) {
|
|
589
|
+
out.flags |= 1 << i;
|
|
590
|
+
out.cbs.push(cb);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return out;
|
|
594
|
+
}
|
|
595
|
+
/** The head + flags argument list of a stream construction/initialization
|
|
596
|
+
* (shared by `new Readable({...})` and a subclass's super(options)),
|
|
597
|
+
* plus the parsed `encoding` option (applied as a follow-up setEncoding). */
|
|
598
|
+
function streamCtorArgs(L, cls, arg, thisType, loc, fallbackCb) {
|
|
599
|
+
const shape = streamCtorShape(cls);
|
|
600
|
+
const o = parseStreamOptions(L, cls.slice(1), arg, thisType, shape.accepted, loc, fallbackCb);
|
|
601
|
+
const head = shape.duplexShape
|
|
602
|
+
? [o.hwmR, o.hwmW, o.autoDestroy, o.emitClose, o.allowHalfOpen,
|
|
603
|
+
boolLit(o.readableSide, loc), boolLit(o.writableSide, loc), f64Lit(o.flags, loc)]
|
|
604
|
+
: [cls === "%Writable" ? o.hwmW : o.hwmR, o.autoDestroy, o.emitClose, f64Lit(o.flags, loc)];
|
|
605
|
+
return { args: [...head, ...o.cbs], encoding: o.encoding };
|
|
606
|
+
}
|
|
607
|
+
/** `new Readable(opts?)` and friends — called from lowerNew once the
|
|
608
|
+
* callee resolved to a builtin stream class. */
|
|
609
|
+
export function lowerStreamNew(L, expr, info) {
|
|
610
|
+
const loc = locOf(expr);
|
|
611
|
+
const cls = info.def.name;
|
|
612
|
+
if (!RUNTIME_STREAM_CLASSES.has(cls))
|
|
613
|
+
L.unsupported("SC1090", expr, `constructing ${cls}`);
|
|
614
|
+
const thisType = { kind: "object", className: cls };
|
|
615
|
+
const args = expr.arguments ?? [];
|
|
616
|
+
if (args.length > 1) {
|
|
617
|
+
L.noLowering(`new ${info.def.name.slice(1)} with ${args.length} arguments`, expr, "the supported form takes one options object");
|
|
618
|
+
}
|
|
619
|
+
if (args[0] !== undefined) {
|
|
620
|
+
const dynOpts = dynOptionsValue(L, args[0]);
|
|
621
|
+
if (dynOpts !== null) {
|
|
622
|
+
return { kind: "libCall", fn: `${streamCtorShape(cls).fn}.newDyn`, args: [dynOpts], type: thisType, loc };
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const fn = `${streamCtorShape(cls).fn}.new`;
|
|
626
|
+
const parsed = streamCtorArgs(L, cls, args[0], thisType, loc);
|
|
627
|
+
const built = { kind: "libCall", fn, args: parsed.args, type: thisType, loc };
|
|
628
|
+
if (parsed.encoding === null)
|
|
629
|
+
return built;
|
|
630
|
+
// { encoding }: chain a setEncoding over the fresh stream (the operand
|
|
631
|
+
// temp releases with the frame; setEncoding answers the receiver +1).
|
|
632
|
+
return { kind: "libCall", fn: "readable.setEncoding", args: [built, strLit(parsed.encoding, loc)], type: thisType, loc };
|
|
633
|
+
}
|
|
634
|
+
/* ── user subclasses (`extends Readable`, the underscore-override form) ── */
|
|
635
|
+
/** The wrapper closure binding one overridden underscore method as the
|
|
636
|
+
* runtime's option callback: a lifted (this, ...prefix) function whose
|
|
637
|
+
* body calls the method — through the vtable when some strict subclass
|
|
638
|
+
* overrides it (the ordinary devirtualization rule), so an inherited
|
|
639
|
+
* constructor still binds the DYNAMIC class's override. The method may
|
|
640
|
+
* declare any PREFIX of its Node signature (the option-callback rule). */
|
|
641
|
+
function streamMethodWrapper(L, info, which, methodName, thisType, loc, blame) {
|
|
642
|
+
const found = L.findMethodOn(info, methodName);
|
|
643
|
+
if (!found) {
|
|
644
|
+
// A DESCENDANT-only declaration (the base chain never declares it):
|
|
645
|
+
// this constructor cannot see it, and silently binding nothing would
|
|
646
|
+
// diverge from Node (which dispatches through the prototype chain).
|
|
647
|
+
if (L.overrideBelow(info, methodName)) {
|
|
648
|
+
L.noLowering(`a subclass of ${info.def.name.replace(/^%/, "")} declaring '${methodName}' below a base whose constructor cannot see it`, blame, `declare '${methodName}' on the class whose constructor calls the stream super(), and override it below`);
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
// An ABSTRACT underscore declaration with no concrete override the
|
|
653
|
+
// devirtualization can reach: no implementation exists to bind (the
|
|
654
|
+
// class itself is abstract, so this constructor only ever runs under a
|
|
655
|
+
// subclass — which, if concrete, implements the method and flips
|
|
656
|
+
// overrideBelow into the virtual branch below).
|
|
657
|
+
if (found.sig.abstract === true && !L.overrideBelow(info, methodName)) {
|
|
658
|
+
L.noLowering(`an abstract '${methodName}' declaration with no concrete implementation below ${info.def.name.replace(/^%/, "")}`, blame, `implement '${methodName}' on a subclass the program constructs`);
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
const tuples = cbTuples(L);
|
|
662
|
+
const full = tuples[which];
|
|
663
|
+
const declared = found.sig.params;
|
|
664
|
+
if (declared.length > full.length) {
|
|
665
|
+
L.noLowering(`a '${methodName}' method declaring ${declared.length} parameters where Node passes ${full.length}`, blame, "stream underscore methods may declare a prefix of their Node signature");
|
|
666
|
+
}
|
|
667
|
+
const paramTypes = [];
|
|
668
|
+
for (let i = 0; i < declared.length; i++) {
|
|
669
|
+
const want = full[i];
|
|
670
|
+
const got = declared[i];
|
|
671
|
+
if (got.mode !== "required") {
|
|
672
|
+
L.noLowering(`a '${methodName}' method with optional or defaulted parameters`, blame, "declare the Node signature's parameters plainly (any prefix is fine)");
|
|
673
|
+
}
|
|
674
|
+
if (got.type.kind === "dyn") {
|
|
675
|
+
// The JS lane's implicitly-any parameters: the emitted invoke
|
|
676
|
+
// thunk boxes each position by kind.
|
|
677
|
+
}
|
|
678
|
+
else if (want === "fn") {
|
|
679
|
+
if (got.type.kind !== "func") {
|
|
680
|
+
L.noLowering(`a '${methodName}' method whose parameter ${i} is '${L.fmt(got.type)}' where Node passes the completion callback`, blame);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
else if (!typeEquals(got.type, want)) {
|
|
684
|
+
L.noLowering(`a '${methodName}' method whose parameter ${i} is '${L.fmt(got.type)}' where Node passes '${L.fmt(want)}'`, blame, "annotate the parameter with the Node signature's type (chunks are Buffers here)");
|
|
685
|
+
}
|
|
686
|
+
paramTypes.push(got.type);
|
|
687
|
+
}
|
|
688
|
+
const ret = found.sig.ret;
|
|
689
|
+
const fnName = `%fn${L.lambdaCounter++}_${which}vt`;
|
|
690
|
+
const funcType = funcOf([thisType, ...paramTypes], ret);
|
|
691
|
+
L.fnStack.push(newFnCtx(true, null, funcType, ret));
|
|
692
|
+
try {
|
|
693
|
+
const thisLocal = L.declareThis(thisType);
|
|
694
|
+
const params = [{ localId: thisLocal.id, name: "this", type: thisType }];
|
|
695
|
+
const argRefs = [];
|
|
696
|
+
for (let i = 0; i < paramTypes.length; i++) {
|
|
697
|
+
const local = L.declareHiddenLocal(`a${i}`, paramTypes[i]);
|
|
698
|
+
params.push({ localId: local.id, name: local.name, type: paramTypes[i] });
|
|
699
|
+
argRefs.push({ kind: "varRef", localId: local.id, type: paramTypes[i], loc });
|
|
700
|
+
}
|
|
701
|
+
const thisRef = { kind: "varRef", localId: thisLocal.id, type: thisType, loc };
|
|
702
|
+
let call;
|
|
703
|
+
if (L.overrideBelow(info, methodName)) {
|
|
704
|
+
L.noteVirtualEdge(info, methodName);
|
|
705
|
+
call = { kind: "virtualCall", className: info.def.name, method: methodName, args: [thisRef, ...argRefs], type: ret, loc };
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
L.noteEdge(`%${found.declarer.def.name}.${methodName}`);
|
|
709
|
+
call = {
|
|
710
|
+
kind: "call",
|
|
711
|
+
callee: `%${found.declarer.def.name}.${methodName}`,
|
|
712
|
+
args: [L.upcastTo(thisRef, found.declarer.def.name), ...argRefs],
|
|
713
|
+
type: ret,
|
|
714
|
+
loc,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
const body = ret.kind === "void"
|
|
718
|
+
? [{ kind: "exprStmt", expr: call, loc }]
|
|
719
|
+
: [{ kind: "return", value: call, loc }];
|
|
720
|
+
appendImplicitUndefinedReturn(L, body, ret, loc);
|
|
721
|
+
const ctx = L.ctx;
|
|
722
|
+
L.liftedFns.push({
|
|
723
|
+
name: fnName,
|
|
724
|
+
params,
|
|
725
|
+
returnType: ret,
|
|
726
|
+
locals: ctx.locals,
|
|
727
|
+
captures: ctx.captures,
|
|
728
|
+
body,
|
|
729
|
+
loc,
|
|
730
|
+
});
|
|
731
|
+
return { kind: "closure", fnName, captures: ctx.captureSources, type: funcType, loc };
|
|
732
|
+
}
|
|
733
|
+
finally {
|
|
734
|
+
L.fnStack.pop();
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
/** `super(options?)` in a class whose direct base chain lands on a
|
|
738
|
+
* runtime stream class: lowers to the stream.init libCall over `this`
|
|
739
|
+
* (the emitted allocation already stamped the subclass vtable and
|
|
740
|
+
* display name; the runtime builds the state block). Options parse
|
|
741
|
+
* exactly like the construction form; overridden underscore methods on
|
|
742
|
+
* the class chain fill callbacks the options leave unset. */
|
|
743
|
+
export function lowerStreamSuperCall(L, info, base, argNodes, thisLocal, loc, blame) {
|
|
744
|
+
const cls = base.def.name;
|
|
745
|
+
const thisType = { kind: "object", className: info.def.name };
|
|
746
|
+
if (argNodes.length > 1) {
|
|
747
|
+
L.noLowering(`super(...) with ${argNodes.length} arguments in a ${cls.slice(1)} subclass`, blame, "the supported form takes one inline options object (or none)");
|
|
748
|
+
}
|
|
749
|
+
const shape = streamCtorShape(cls);
|
|
750
|
+
const fallback = (name) => {
|
|
751
|
+
const methodName = UNDERSCORE_METHODS.get(name);
|
|
752
|
+
return methodName ? streamMethodWrapper(L, info, name, methodName, thisType, loc, blame) : null;
|
|
753
|
+
};
|
|
754
|
+
const thisRef = () => ({ kind: "varRef", localId: thisLocal.id, type: thisType, loc });
|
|
755
|
+
if (argNodes[0] !== undefined) {
|
|
756
|
+
const dynOpts = dynOptionsValue(L, argNodes[0]);
|
|
757
|
+
if (dynOpts !== null) {
|
|
758
|
+
// The dyn-options twin: the runtime walks the record; the class's
|
|
759
|
+
// overridden underscore methods ride as FALLBACK wrappers (an
|
|
760
|
+
// options callback shadows them, Node's instance-property rule).
|
|
761
|
+
let flags = 0;
|
|
762
|
+
const cbs = [];
|
|
763
|
+
for (let i = 0; i < shape.accepted.length; i++) {
|
|
764
|
+
const cb = fallback(shape.accepted[i]);
|
|
765
|
+
if (cb) {
|
|
766
|
+
flags |= 1 << i;
|
|
767
|
+
cbs.push(cb);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return [{
|
|
771
|
+
kind: "exprStmt",
|
|
772
|
+
expr: {
|
|
773
|
+
kind: "libCall",
|
|
774
|
+
fn: `${shape.fn}.initDyn`,
|
|
775
|
+
args: [thisRef(), dynOpts, f64Lit(flags, loc), ...cbs],
|
|
776
|
+
type: VOID,
|
|
777
|
+
loc,
|
|
778
|
+
},
|
|
779
|
+
loc,
|
|
780
|
+
}];
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
const parsed = streamCtorArgs(L, cls, argNodes[0], thisType, loc, fallback);
|
|
784
|
+
const out = [{
|
|
785
|
+
kind: "exprStmt",
|
|
786
|
+
expr: { kind: "libCall", fn: `${shape.fn}.init`, args: [thisRef(), ...parsed.args], type: VOID, loc },
|
|
787
|
+
loc,
|
|
788
|
+
}];
|
|
789
|
+
if (parsed.encoding !== null) {
|
|
790
|
+
out.push({
|
|
791
|
+
kind: "exprStmt",
|
|
792
|
+
expr: { kind: "libCall", fn: "readable.setEncoding", args: [thisRef(), strLit(parsed.encoding, loc)], type: thisType, loc },
|
|
793
|
+
loc,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
return out;
|
|
797
|
+
}
|
|
798
|
+
/* ── the underscore-method assignment surface ─────────────────────────── */
|
|
799
|
+
/** `r._read = fn` / `w._write = fn` (and _final/_destroy/_transform/
|
|
800
|
+
* _flush) on a stream-rooted receiver AFTER construction — Node assigns
|
|
801
|
+
* an own property that shadows the prototype method and the machinery
|
|
802
|
+
* calls through it from then on. Here the runtime slot the option
|
|
803
|
+
* callbacks fill swaps its closure: the RHS lowers exactly like the
|
|
804
|
+
* matching option callback (inline functions fully static with the
|
|
805
|
+
* stream as `this`; function VALUES — common.mustCall(fn) — through the
|
|
806
|
+
* checked-dynamic boundary with the documented this-undefined
|
|
807
|
+
* divergence), and the next _read/_write/... dispatch uses it, Node's
|
|
808
|
+
* own timing. Null when the LHS is not an underscore-method write on a
|
|
809
|
+
* runtime-stream-rooted receiver (the caller's routes continue);
|
|
810
|
+
* `_writev` and methods outside the receiver's base set keep pointed
|
|
811
|
+
* fences (Node would consume them through unlowered machinery). */
|
|
812
|
+
export function lowerStreamUnderscoreAssign(L, expr) {
|
|
813
|
+
if (!ts.isPropertyAccessExpression(expr.left) || expr.left.questionDotToken)
|
|
814
|
+
return null;
|
|
815
|
+
const methodName = expr.left.name.text;
|
|
816
|
+
if (!methodName.startsWith("_"))
|
|
817
|
+
return null;
|
|
818
|
+
const optionByMethod = new Map(Array.from(UNDERSCORE_METHODS, ([o, m]) => [m, o]));
|
|
819
|
+
if (!optionByMethod.has(methodName) && methodName !== "_writev" && methodName !== "_construct")
|
|
820
|
+
return null;
|
|
821
|
+
const recv = probeLower(L, expr.left.expression);
|
|
822
|
+
if (!recv || recv.type.kind !== "object")
|
|
823
|
+
return null;
|
|
824
|
+
const info = L.classes.get(recv.type.className);
|
|
825
|
+
let base = null;
|
|
826
|
+
for (let c = info ?? null; c; c = c.base) {
|
|
827
|
+
if (RUNTIME_STREAM_CLASSES.has(c.def.name)) {
|
|
828
|
+
base = c;
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (!base)
|
|
833
|
+
return null;
|
|
834
|
+
const loc = locOf(expr);
|
|
835
|
+
const cls = base.def.name;
|
|
836
|
+
const clsName = cls.slice(1);
|
|
837
|
+
if (methodName === "_writev" || methodName === "_construct") {
|
|
838
|
+
L.noLowering(`assigning '${methodName}' on a ${clsName}`, expr.left, methodName === "_writev"
|
|
839
|
+
? "batched writes (_writev) are not lowered yet — writes deliver one chunk at a time"
|
|
840
|
+
: "deferred construction (_construct) is not lowered yet");
|
|
841
|
+
}
|
|
842
|
+
const which = optionByMethod.get(methodName);
|
|
843
|
+
const { accepted } = streamCtorShape(cls);
|
|
844
|
+
if (!accepted.includes(which)) {
|
|
845
|
+
L.noLowering(`assigning '${methodName}' on a ${clsName}`, expr.left, `a ${clsName}'s machinery consumes ${accepted.map((a) => `'${UNDERSCORE_METHODS.get(a)}'`).join("/")} — '${methodName}' would ride machinery with no lowering here`);
|
|
846
|
+
}
|
|
847
|
+
const thisType = recv.type;
|
|
848
|
+
const tuples = cbTuples(L);
|
|
849
|
+
let cb;
|
|
850
|
+
const rhs = expr.right;
|
|
851
|
+
if (ts.isFunctionExpression(rhs) || ts.isArrowFunction(rhs)) {
|
|
852
|
+
const full = tuples[which].map((t) => (t === "fn" ? null : t));
|
|
853
|
+
const { funcType } = L.lambdaSignature(rhs);
|
|
854
|
+
const tuple = full.map((t, i) => {
|
|
855
|
+
if (t !== null)
|
|
856
|
+
return t;
|
|
857
|
+
const declared = funcType.params[i];
|
|
858
|
+
if (declared !== undefined && declared.kind !== "func" && declared.kind !== "dyn") {
|
|
859
|
+
L.noLowering(`a '${methodName}' callback whose parameter ${i} is '${L.fmt(declared)}' where Node passes the completion callback`, rhs);
|
|
860
|
+
}
|
|
861
|
+
return declared ?? funcOf([], VOID);
|
|
862
|
+
});
|
|
863
|
+
cb = lowerStreamCallback(L, which, rhs, thisType, tuple);
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
cb = lowerStreamCallbackValue(L, clsName, which, rhs, tuples[which].length);
|
|
867
|
+
}
|
|
868
|
+
const SET_FNS = {
|
|
869
|
+
read: "stream.setRead",
|
|
870
|
+
write: "stream.setWrite",
|
|
871
|
+
final: "stream.setFinal",
|
|
872
|
+
destroy: "stream.setDestroy",
|
|
873
|
+
transform: "stream.setTransform",
|
|
874
|
+
flush: "stream.setFlush",
|
|
875
|
+
};
|
|
876
|
+
return {
|
|
877
|
+
kind: "exprStmt",
|
|
878
|
+
expr: { kind: "libCall", fn: SET_FNS[which], args: [recv, cb], type: VOID, loc },
|
|
879
|
+
loc,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
/* ── the static surface (Readable.from) ───────────────────────────────── */
|
|
883
|
+
/** `Readable.from(iterable)` — the array-seeded object-entry form:
|
|
884
|
+
* string[] / Buffer[] values (or literals) become a fully-seeded
|
|
885
|
+
* readable delivering one WHOLE chunk per element (Node's objectMode
|
|
886
|
+
* accounting; hwm 1), and a single string/Buffer argument is a
|
|
887
|
+
* one-chunk stream (Node's special case). Called from the method-call
|
|
888
|
+
* chain once the receiver resolved to a stream class value. */
|
|
889
|
+
export function lowerStreamStaticCall(L, call, access) {
|
|
890
|
+
if (call.questionDotToken || access.questionDotToken)
|
|
891
|
+
return null;
|
|
892
|
+
let symbol = null;
|
|
893
|
+
if (ts.isIdentifier(access.expression)) {
|
|
894
|
+
symbol = L.resolveValueSymbol(access.expression);
|
|
895
|
+
}
|
|
896
|
+
else if (ts.isPropertyAccessExpression(access.expression) && ts.isIdentifier(access.expression.name)) {
|
|
897
|
+
const memberSym = L.checker.getSymbolAtLocation(access.expression.name);
|
|
898
|
+
symbol = memberSym && memberSym.flags & ts.SymbolFlags.Alias
|
|
899
|
+
? L.checker.getAliasedSymbol(memberSym)
|
|
900
|
+
: memberSym;
|
|
901
|
+
}
|
|
902
|
+
if (!symbol)
|
|
903
|
+
return null;
|
|
904
|
+
const info = L.builtinStreamInfoOf(symbol);
|
|
905
|
+
if (!info)
|
|
906
|
+
return null;
|
|
907
|
+
const member = access.name.text;
|
|
908
|
+
const loc = locOf(call);
|
|
909
|
+
const cls = info.def.name;
|
|
910
|
+
if (member !== "from") {
|
|
911
|
+
L.noLowering(`${cls.slice(1)}.${member}`, call);
|
|
912
|
+
}
|
|
913
|
+
if (cls !== "%Readable") {
|
|
914
|
+
L.noLowering(`${cls.slice(1)}.from`, call, "Readable.from is the lowered form");
|
|
915
|
+
}
|
|
916
|
+
const args = call.arguments;
|
|
917
|
+
if (args.length !== 1) {
|
|
918
|
+
L.noLowering(`Readable.from with ${args.length} arguments`, call, "the one-argument form (an array, string, or Buffer) is supported");
|
|
919
|
+
}
|
|
920
|
+
const srcT = L.mapTypeOf(L.typeOf(args[0]));
|
|
921
|
+
const type = { kind: "object", className: "%Readable" };
|
|
922
|
+
if (srcT?.kind === "array" && (srcT.elem.kind === "string" || (srcT.elem.kind === "bytes" && srcT.elem.elem === "u8"))) {
|
|
923
|
+
const arr = L.lowerExpr(args[0]);
|
|
924
|
+
const strings = srcT.elem.kind === "string";
|
|
925
|
+
return { kind: "libCall", fn: "readable.fromArr", args: [arr, boolLit(strings, loc)], type, loc };
|
|
926
|
+
}
|
|
927
|
+
if (srcT?.kind === "string" || (srcT?.kind === "bytes" && srcT.elem === "u8")) {
|
|
928
|
+
// Node special-cases a single string/Buffer: ONE whole chunk.
|
|
929
|
+
const v = L.lowerExpr(args[0]);
|
|
930
|
+
const strings = srcT.kind === "string";
|
|
931
|
+
const arr = { kind: "arrayLit", elems: [v], type: arrayOf(v.type), loc };
|
|
932
|
+
return { kind: "libCall", fn: "readable.fromArr", args: [arr, boolLit(strings, loc)], type, loc };
|
|
933
|
+
}
|
|
934
|
+
L.noLowering(`Readable.from over a '${srcT ? L.fmt(srcT) : "?"}' source`, args[0], "string[] / Buffer[] arrays (and a single string or Buffer) are the supported sources — generators and async iterables have no lowering yet");
|
|
935
|
+
}
|
|
936
|
+
/** `const Writable = stream.Writable` — a runtime stream CLASS bound
|
|
937
|
+
* through a namespace member: alias plumbing with no storage (both
|
|
938
|
+
* declaration walks skip it; uses resolve through builtinStreamInfoOf's
|
|
939
|
+
* alias following). Only the five stream class names count — value
|
|
940
|
+
* members (stream.getDefaultHighWaterMark as a value, ...) keep their
|
|
941
|
+
* per-site fences. */
|
|
942
|
+
export function streamClassAliasDecl(L, nameNode, init) {
|
|
943
|
+
if (!ts.isIdentifier(nameNode) || !init)
|
|
944
|
+
return false;
|
|
945
|
+
if (!ts.isPropertyAccessExpression(init) || init.questionDotToken)
|
|
946
|
+
return false;
|
|
947
|
+
let isStreamClass = false;
|
|
948
|
+
for (const [, rec] of RUNTIME_STREAM_CLASSES) {
|
|
949
|
+
if (rec.lib === init.name.text)
|
|
950
|
+
isStreamClass = true;
|
|
951
|
+
}
|
|
952
|
+
if (!isStreamClass)
|
|
953
|
+
return false;
|
|
954
|
+
return L.builtinNamespaceModuleOf(init.expression) === "stream";
|
|
955
|
+
}
|
|
956
|
+
/* ── the module functions (finished / pipeline — the callback forms) ──── */
|
|
957
|
+
/** A stream-rooted argument, lowered — or the pointed fence (pipeline's
|
|
958
|
+
* iterable/generator/function stages have no lowering yet). */
|
|
959
|
+
function lowerStreamArg(L, node, what) {
|
|
960
|
+
const v = L.lowerExpr(node);
|
|
961
|
+
const info = v.type.kind === "object" ? L.classes.get(v.type.className) : undefined;
|
|
962
|
+
if (!streamSidesOf(L, info)) {
|
|
963
|
+
L.noLowering(`${what} over a '${L.fmt(v.type)}'`, node, "stream arguments are the lowered surface (iterables, generators, and plain functions have no lowering yet)");
|
|
964
|
+
}
|
|
965
|
+
return v;
|
|
966
|
+
}
|
|
967
|
+
/** The finished/pipeline completion callback: an inline function lowers
|
|
968
|
+
* through the option-callback machinery (leading `this`, an `Error |
|
|
969
|
+
* null` prefix — Node binds the stream and passes the error); any other
|
|
970
|
+
* value adapts through the checked-dynamic boundary (mustCall wrappers —
|
|
971
|
+
* the runtime inv calls it with NO arguments on success, Node's eos
|
|
972
|
+
* convention). */
|
|
973
|
+
function lowerEosCallback(L, which, node, thisType) {
|
|
974
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
975
|
+
// The error parameter takes what the user declared when it is an
|
|
976
|
+
// Error/null/undefined union (@types spells `ErrnoException | null |
|
|
977
|
+
// undefined`); the emitted thunk picks the undefined arm on success.
|
|
978
|
+
let tuple = [errorOrNull(L)];
|
|
979
|
+
try {
|
|
980
|
+
const { funcType } = L.lambdaSignature(node);
|
|
981
|
+
const p0 = funcType.params[0];
|
|
982
|
+
if (p0?.kind === "union") {
|
|
983
|
+
const def = L.unions.get(p0.unionId);
|
|
984
|
+
if (def && def.arms.some((a) => a.kind === "object" && a.className === "%Error") &&
|
|
985
|
+
def.arms.every((a) => a.kind === "nullT" || a.kind === "undefinedT" ||
|
|
986
|
+
(a.kind === "object" && a.className === "%Error"))) {
|
|
987
|
+
tuple = [p0];
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
catch {
|
|
992
|
+
/* the fixed tuple speaks */
|
|
993
|
+
}
|
|
994
|
+
return { cb: lowerStreamCallback(L, which, node, thisType, tuple), dyn: false };
|
|
995
|
+
}
|
|
996
|
+
const getRecord = (id) => L.shapes.get(id);
|
|
997
|
+
const getUnion = (id) => L.unions.get(id);
|
|
998
|
+
let t;
|
|
999
|
+
try {
|
|
1000
|
+
const ct = L.typeOf(node);
|
|
1001
|
+
t = L.mapTypeOf(ct) ?? dynFallbackType(L, node, ct);
|
|
1002
|
+
}
|
|
1003
|
+
catch {
|
|
1004
|
+
t = null;
|
|
1005
|
+
}
|
|
1006
|
+
if (t?.kind === "dyn") {
|
|
1007
|
+
const v = L.lowerExpr(node);
|
|
1008
|
+
if (v.type.kind === "dyn")
|
|
1009
|
+
return { cb: v, dyn: true };
|
|
1010
|
+
}
|
|
1011
|
+
else if (t?.kind === "func") {
|
|
1012
|
+
const cb = L.lowerExpr(node);
|
|
1013
|
+
if (cb.type.kind === "func" && canBoxFuncIntoDyn(cb.type, getRecord, getUnion)) {
|
|
1014
|
+
return { cb: { kind: "dynFrom", value: cb, type: DYN, loc: locOf(node) }, dyn: true };
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
L.noLowering(`${which} with this callback value`, node, "write the callback inline, or pass a function value that crosses the checked-dynamic boundary");
|
|
1018
|
+
}
|
|
1019
|
+
/** `finished(...)` / `pipeline(...)` / `getDefaultHighWaterMark(...)`
|
|
1020
|
+
* imported from node:stream (named imports, destructured requires, and
|
|
1021
|
+
* namespace-member calls all land here). Null for members this spoke
|
|
1022
|
+
* does not own — the caller's module-qualified fence speaks. */
|
|
1023
|
+
export function lowerStreamModuleCall(L, call, bi, loc) {
|
|
1024
|
+
if (bi.module === "stream/promises") {
|
|
1025
|
+
if (bi.member === "finished" || bi.member === "pipeline") {
|
|
1026
|
+
L.noLowering(`stream/promises.${bi.member}`, call, `the callback form of ${bi.member} from 'stream' is lowered; the promise form is not yet`);
|
|
1027
|
+
}
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
if (bi.module !== "stream")
|
|
1031
|
+
return null;
|
|
1032
|
+
const args = call.arguments;
|
|
1033
|
+
if (bi.member === "getDefaultHighWaterMark") {
|
|
1034
|
+
// Node 24: 65536 bytes / 16 objects — compile-time literals only.
|
|
1035
|
+
// The byte default is platform-split at Node's source
|
|
1036
|
+
// (lib/internal/streams/state.js: win32 keeps 16 KiB), so the fold
|
|
1037
|
+
// follows the TARGET platform like the path-module binding.
|
|
1038
|
+
if (args.length === 1 && args[0].kind === ts.SyntaxKind.FalseKeyword) {
|
|
1039
|
+
return f64Lit(L.targetPlatform === "win32" ? 16384 : 65536, loc);
|
|
1040
|
+
}
|
|
1041
|
+
if (args.length === 1 && args[0].kind === ts.SyntaxKind.TrueKeyword)
|
|
1042
|
+
return f64Lit(16, loc);
|
|
1043
|
+
L.noLowering("getDefaultHighWaterMark with a non-literal argument", call, "the objectMode flag must be a compile-time boolean");
|
|
1044
|
+
}
|
|
1045
|
+
if (bi.member === "finished") {
|
|
1046
|
+
if (args.length !== 2) {
|
|
1047
|
+
L.noLowering(`finished with ${args.length} arguments`, call, args.length === 3 ? "the options argument has no lowering yet — the two-argument form is supported" : "the supported form is finished(stream, callback)");
|
|
1048
|
+
}
|
|
1049
|
+
const recv = lowerStreamArg(L, args[0], "finished");
|
|
1050
|
+
const { cb, dyn } = lowerEosCallback(L, "finished", args[1], recv.type);
|
|
1051
|
+
return {
|
|
1052
|
+
kind: "libCall",
|
|
1053
|
+
fn: dyn ? "stream.finishedDyn" : "stream.finished",
|
|
1054
|
+
args: [recv, cb],
|
|
1055
|
+
type: funcOf([], VOID),
|
|
1056
|
+
loc,
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
if (bi.member === "pipeline") {
|
|
1060
|
+
if (args.length < 3) {
|
|
1061
|
+
L.noLowering(`pipeline with ${args.length} arguments`, call, "the supported form is pipeline(source, ...streams, callback)");
|
|
1062
|
+
}
|
|
1063
|
+
const streams = args.slice(0, -1).map((a) => lowerStreamArg(L, a, "pipeline"));
|
|
1064
|
+
const last = streams[streams.length - 1];
|
|
1065
|
+
const { cb, dyn } = lowerEosCallback(L, "pipeline", args[args.length - 1], last.type);
|
|
1066
|
+
return {
|
|
1067
|
+
kind: "libCall",
|
|
1068
|
+
fn: dyn ? "stream.pipelineDyn" : "stream.pipeline",
|
|
1069
|
+
args: [f64Lit(streams.length, loc), ...streams, cb],
|
|
1070
|
+
type: last.type,
|
|
1071
|
+
loc,
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
/* ── the method surface ───────────────────────────────────────────────── */
|
|
1077
|
+
/** The one supported encoding argument: utf8 (Node's default), as a
|
|
1078
|
+
* compile-time literal or absent. */
|
|
1079
|
+
function checkUtf8Encoding(L, member, arg) {
|
|
1080
|
+
if (arg === undefined)
|
|
1081
|
+
return;
|
|
1082
|
+
const t = L.typeOf(arg);
|
|
1083
|
+
if (t.isStringLiteralType() && (t.value === "utf8" || t.value === "utf-8"))
|
|
1084
|
+
return;
|
|
1085
|
+
L.noLowering(`${member} with a ${t.isStringLiteralType() ? `'${t.value}'` : "non-literal"} encoding`, arg, "utf8 (as a compile-time literal) is the supported encoding");
|
|
1086
|
+
}
|
|
1087
|
+
/** `recv.<member>(...)` over a stream-rooted receiver. Returns null only
|
|
1088
|
+
* for members this spoke does not own. */
|
|
1089
|
+
export function lowerStreamMethodCall(L, call, access, info) {
|
|
1090
|
+
const member = access.name.text;
|
|
1091
|
+
if (!STREAM_API_MEMBERS.has(member))
|
|
1092
|
+
return null;
|
|
1093
|
+
const sides = streamSidesOf(L, info);
|
|
1094
|
+
if (!sides)
|
|
1095
|
+
return null;
|
|
1096
|
+
const loc = locOf(call);
|
|
1097
|
+
const args = call.arguments;
|
|
1098
|
+
const readableSide = sides === "r" || sides === "rw";
|
|
1099
|
+
const writableSide = sides === "w" || sides === "rw";
|
|
1100
|
+
const cls = info.def.name.slice(1);
|
|
1101
|
+
const requireSide = (need, what) => {
|
|
1102
|
+
if (!need) {
|
|
1103
|
+
L.noLowering(`${what} on a ${cls} (the ${what.startsWith("write") || what === "end" || what === "cork" || what === "uncork" ? "writable" : "readable"} half is absent)`, call);
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
// Chunk classification for push/unshift/write/end. Union-typed chunks
|
|
1107
|
+
// over {Buffer, string, null} dispatch by tag at runtime — the
|
|
1108
|
+
// `push(more ? next : null)` idiom.
|
|
1109
|
+
const chunkKind = (node) => {
|
|
1110
|
+
if (node.kind === ts.SyntaxKind.NullKeyword)
|
|
1111
|
+
return "null";
|
|
1112
|
+
const t = L.mapTypeOf(L.typeOf(node));
|
|
1113
|
+
if (t?.kind === "bytes" && t.elem === "u8")
|
|
1114
|
+
return "bytes";
|
|
1115
|
+
if (t?.kind === "string")
|
|
1116
|
+
return "string";
|
|
1117
|
+
if (t?.kind === "nullT")
|
|
1118
|
+
return "null";
|
|
1119
|
+
if (t?.kind === "dyn") {
|
|
1120
|
+
// The JS lane's any-typed chunk: the runtime dispatches by DOM tag
|
|
1121
|
+
// (bytes / string / null; anything else is the write TypeError).
|
|
1122
|
+
return "dyn";
|
|
1123
|
+
}
|
|
1124
|
+
if (t?.kind === "union") {
|
|
1125
|
+
const def = L.unions.get(t.unionId);
|
|
1126
|
+
if (def && def.arms.every((a) => (a.kind === "bytes" && a.elem === "u8") || a.kind === "string" || a.kind === "nullT")) {
|
|
1127
|
+
return "union";
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
if (t === null) {
|
|
1131
|
+
// A checker-untyped chunk (the JS lane's `t.write(x.toString())`
|
|
1132
|
+
// shapes): the LOWERING may still land on a supported kind — probe
|
|
1133
|
+
// it (the probe's IR is discarded; the caller re-lowers the node).
|
|
1134
|
+
const probed = probeLower(L, node);
|
|
1135
|
+
if (probed?.type.kind === "string")
|
|
1136
|
+
return "string";
|
|
1137
|
+
if (probed?.type.kind === "bytes" && probed.type.elem === "u8")
|
|
1138
|
+
return "bytes";
|
|
1139
|
+
if (probed?.type.kind === "dyn")
|
|
1140
|
+
return "dyn";
|
|
1141
|
+
if (probed?.type.kind === "nullT")
|
|
1142
|
+
return "null";
|
|
1143
|
+
}
|
|
1144
|
+
L.noLowering(`${member} with a '${t ? L.fmt(t) : "?"}' chunk`, node, "chunks are Buffers or utf8 strings — or unions of those with null (objectMode is not lowered)");
|
|
1145
|
+
};
|
|
1146
|
+
if (member === "push" || member === "unshift") {
|
|
1147
|
+
requireSide(readableSide, member);
|
|
1148
|
+
if (args.length === 0 || args.length > 2) {
|
|
1149
|
+
L.noLowering(`${member} with ${args.length} arguments`, call, "the supported forms are (chunk) and (chunk, \"utf8\")");
|
|
1150
|
+
}
|
|
1151
|
+
checkUtf8Encoding(L, member, args[1]);
|
|
1152
|
+
const kind = chunkKind(args[0]);
|
|
1153
|
+
if ((kind === "null" || kind === "union" || kind === "dyn") && member === "unshift") {
|
|
1154
|
+
L.noLowering(kind === "null" ? "unshift(null)" : "unshift with a nullable or dynamic chunk", call, "EOF signals through push(null)");
|
|
1155
|
+
}
|
|
1156
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1157
|
+
if (kind === "null") {
|
|
1158
|
+
return { kind: "libCall", fn: "readable.pushNull", args: [receiver], type: BOOL, loc };
|
|
1159
|
+
}
|
|
1160
|
+
if (kind === "dyn") {
|
|
1161
|
+
const chunk = L.lowerExprExpecting(args[0], DYN);
|
|
1162
|
+
return { kind: "libCall", fn: "readable.pushDyn", args: [receiver, chunk], type: BOOL, loc };
|
|
1163
|
+
}
|
|
1164
|
+
if (kind === "union") {
|
|
1165
|
+
// The lowered value may have collapsed to one arm (checker
|
|
1166
|
+
// narrowing) — re-dispatch on its IR type.
|
|
1167
|
+
const chunk = L.lowerExpr(args[0]);
|
|
1168
|
+
if (chunk.type.kind === "union") {
|
|
1169
|
+
return { kind: "libCall", fn: "readable.pushU", args: [receiver, chunk], type: BOOL, loc };
|
|
1170
|
+
}
|
|
1171
|
+
if (chunk.type.kind === "bytes") {
|
|
1172
|
+
return { kind: "libCall", fn: "readable.push", args: [receiver, chunk], type: BOOL, loc };
|
|
1173
|
+
}
|
|
1174
|
+
if (chunk.type.kind === "string") {
|
|
1175
|
+
return { kind: "libCall", fn: "readable.pushStr", args: [receiver, chunk], type: BOOL, loc };
|
|
1176
|
+
}
|
|
1177
|
+
return { kind: "libCall", fn: "readable.pushNull", args: [receiver], type: BOOL, loc };
|
|
1178
|
+
}
|
|
1179
|
+
const chunk = kind === "bytes" ? L.lowerExprExpecting(args[0], BYTES) : L.lowerExprExpecting(args[0], STRING);
|
|
1180
|
+
const fn = member === "push"
|
|
1181
|
+
? (kind === "bytes" ? "readable.push" : "readable.pushStr")
|
|
1182
|
+
: (kind === "bytes" ? "readable.unshift" : "readable.unshiftStr");
|
|
1183
|
+
return { kind: "libCall", fn, args: [receiver, chunk], type: member === "push" ? BOOL : VOID, loc };
|
|
1184
|
+
}
|
|
1185
|
+
if (member === "read") {
|
|
1186
|
+
requireSide(readableSide, "read");
|
|
1187
|
+
if (args.length > 1)
|
|
1188
|
+
L.noLowering(`read with ${args.length} arguments`, call);
|
|
1189
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1190
|
+
const size = args[0] ? L.lowerExprExpecting(args[0], F64) : f64Lit(-1, loc);
|
|
1191
|
+
const type = unionOf(L, [BYTES, { kind: "nullT" }]);
|
|
1192
|
+
const read = { kind: "libCall", fn: "readable.read", args: [receiver, size], type, loc };
|
|
1193
|
+
return L.maybeNarrow(read, call);
|
|
1194
|
+
}
|
|
1195
|
+
if (member === "pause" || member === "resume") {
|
|
1196
|
+
requireSide(readableSide, member);
|
|
1197
|
+
if (args.length !== 0)
|
|
1198
|
+
L.noLowering(`${member} with arguments`, call);
|
|
1199
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1200
|
+
return { kind: "libCall", fn: member === "pause" ? "readable.pause" : "readable.resume", args: [receiver], type: receiver.type, loc };
|
|
1201
|
+
}
|
|
1202
|
+
if (member === "isPaused") {
|
|
1203
|
+
requireSide(readableSide, "isPaused");
|
|
1204
|
+
if (args.length !== 0)
|
|
1205
|
+
L.noLowering(`isPaused with arguments`, call);
|
|
1206
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1207
|
+
return { kind: "libCall", fn: "readable.isPaused", args: [receiver], type: BOOL, loc };
|
|
1208
|
+
}
|
|
1209
|
+
if (member === "pipe") {
|
|
1210
|
+
requireSide(readableSide, "pipe");
|
|
1211
|
+
if (args.length === 0 || args.length > 2) {
|
|
1212
|
+
L.noLowering(`pipe with ${args.length} arguments`, call, "the supported forms are (destination) and (destination, { end })");
|
|
1213
|
+
}
|
|
1214
|
+
const dst = L.lowerExpr(args[0]);
|
|
1215
|
+
const dstInfo = dst.type.kind === "object" ? L.classes.get(dst.type.className) : undefined;
|
|
1216
|
+
const dstSides = streamSidesOf(L, dstInfo);
|
|
1217
|
+
if (dstSides !== "w" && dstSides !== "rw") {
|
|
1218
|
+
L.noLowering(`pipe into a '${L.fmt(dst.type)}'`, args[0], "the destination must be a stream Writable/Duplex (process streams and sockets are not pipe destinations yet)");
|
|
1219
|
+
}
|
|
1220
|
+
let end = boolLit(true, loc);
|
|
1221
|
+
if (args[1] !== undefined) {
|
|
1222
|
+
if (!ts.isObjectLiteralExpression(args[1])) {
|
|
1223
|
+
L.noLowering("pipe with a non-literal options argument", args[1], "{ end: <bool> } inline is the supported form");
|
|
1224
|
+
}
|
|
1225
|
+
for (const prop of args[1].properties) {
|
|
1226
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== "end") {
|
|
1227
|
+
L.noLowering("a pipe option other than 'end'", prop);
|
|
1228
|
+
}
|
|
1229
|
+
end = L.lowerExprExpecting(prop.initializer, BOOL);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1233
|
+
return { kind: "libCall", fn: "readable.pipe", args: [receiver, dst, end], type: dst.type, loc };
|
|
1234
|
+
}
|
|
1235
|
+
if (member === "unpipe") {
|
|
1236
|
+
requireSide(readableSide, "unpipe");
|
|
1237
|
+
if (args.length > 1)
|
|
1238
|
+
L.noLowering(`unpipe with ${args.length} arguments`, call);
|
|
1239
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1240
|
+
if (args[0] !== undefined) {
|
|
1241
|
+
const dst = L.lowerExpr(args[0]);
|
|
1242
|
+
const dstInfo = dst.type.kind === "object" ? L.classes.get(dst.type.className) : undefined;
|
|
1243
|
+
const dstSides = streamSidesOf(L, dstInfo);
|
|
1244
|
+
if (dstSides !== "w" && dstSides !== "rw") {
|
|
1245
|
+
L.noLowering(`unpipe of a '${L.fmt(dst.type)}'`, args[0]);
|
|
1246
|
+
}
|
|
1247
|
+
return { kind: "libCall", fn: "readable.unpipe", args: [receiver, dst], type: receiver.type, loc };
|
|
1248
|
+
}
|
|
1249
|
+
return { kind: "libCall", fn: "readable.unpipe", args: [receiver], type: receiver.type, loc };
|
|
1250
|
+
}
|
|
1251
|
+
if (member === "destroy") {
|
|
1252
|
+
if (args.length > 1)
|
|
1253
|
+
L.noLowering(`destroy with ${args.length} arguments`, call);
|
|
1254
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1255
|
+
if (args[0] !== undefined) {
|
|
1256
|
+
const err = L.lowerExpr(args[0]);
|
|
1257
|
+
const rootsAtError = (t) => {
|
|
1258
|
+
if (t.kind !== "object")
|
|
1259
|
+
return false;
|
|
1260
|
+
for (let c = L.classes.get(t.className) ?? null; c; c = c.base) {
|
|
1261
|
+
if (c.def.name === "%Error")
|
|
1262
|
+
return true;
|
|
1263
|
+
}
|
|
1264
|
+
return false;
|
|
1265
|
+
};
|
|
1266
|
+
if (!rootsAtError(err.type)) {
|
|
1267
|
+
L.noLowering(`destroy with a '${L.fmt(err.type)}' argument`, args[0], "the supported argument is an Error-hierarchy instance");
|
|
1268
|
+
}
|
|
1269
|
+
return { kind: "libCall", fn: "stream.destroyErr", args: [receiver, L.upcastTo(err, "%Error")], type: receiver.type, loc };
|
|
1270
|
+
}
|
|
1271
|
+
return { kind: "libCall", fn: "stream.destroy", args: [receiver], type: receiver.type, loc };
|
|
1272
|
+
}
|
|
1273
|
+
if (member === "write") {
|
|
1274
|
+
requireSide(writableSide, "write");
|
|
1275
|
+
if (args.length === 0 || args.length > 3) {
|
|
1276
|
+
L.noLowering(`write with ${args.length} arguments`, call);
|
|
1277
|
+
}
|
|
1278
|
+
const kind = chunkKind(args[0]);
|
|
1279
|
+
if (kind === "null")
|
|
1280
|
+
L.noLowering("write(null)", call, "end() finishes a writable");
|
|
1281
|
+
if (kind === "dyn") {
|
|
1282
|
+
if (args.length > 1)
|
|
1283
|
+
L.noLowering("write with a dynamic chunk and more arguments", call);
|
|
1284
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1285
|
+
const chunk = L.lowerExprExpecting(args[0], DYN);
|
|
1286
|
+
return { kind: "libCall", fn: "writable.writeDyn", args: [receiver, chunk], type: BOOL, loc };
|
|
1287
|
+
}
|
|
1288
|
+
if (kind === "union") {
|
|
1289
|
+
// Bytes/string arms dispatch at runtime; a null arm value throws
|
|
1290
|
+
// Node's ERR_STREAM_NULL_VALUES TypeError in the runtime. The
|
|
1291
|
+
// lowered value may have collapsed to one arm (checker narrowing) —
|
|
1292
|
+
// re-dispatch on its IR type.
|
|
1293
|
+
if (args.length > 1)
|
|
1294
|
+
L.noLowering("write with a union chunk and more arguments", call);
|
|
1295
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1296
|
+
const chunk = L.lowerExpr(args[0]);
|
|
1297
|
+
const fn = chunk.type.kind === "union" ? "writable.writeU"
|
|
1298
|
+
: chunk.type.kind === "string" ? "writable.writeStr" : "writable.write";
|
|
1299
|
+
if (chunk.type.kind === "nullT")
|
|
1300
|
+
L.noLowering("write(null)", call, "end() finishes a writable");
|
|
1301
|
+
return { kind: "libCall", fn, args: [receiver, chunk], type: BOOL, loc };
|
|
1302
|
+
}
|
|
1303
|
+
// Disambiguate (chunk, cb?) vs (chunk, encoding, cb?) by the second
|
|
1304
|
+
// argument's static type.
|
|
1305
|
+
let cbArg;
|
|
1306
|
+
if (args.length === 2) {
|
|
1307
|
+
const t = L.mapTypeOf(L.typeOf(args[1]));
|
|
1308
|
+
if (t?.kind === "string")
|
|
1309
|
+
checkUtf8Encoding(L, "write", args[1]);
|
|
1310
|
+
else
|
|
1311
|
+
cbArg = args[1];
|
|
1312
|
+
}
|
|
1313
|
+
else if (args.length === 3) {
|
|
1314
|
+
checkUtf8Encoding(L, "write", args[1]);
|
|
1315
|
+
cbArg = args[2];
|
|
1316
|
+
}
|
|
1317
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1318
|
+
const chunk = kind === "bytes" ? L.lowerExprExpecting(args[0], BYTES) : L.lowerExprExpecting(args[0], STRING);
|
|
1319
|
+
const fn = kind === "bytes" ? "writable.write" : "writable.writeStr";
|
|
1320
|
+
if (cbArg !== undefined) {
|
|
1321
|
+
const cb = L.lowerExprExpecting(cbArg, funcOf([], VOID));
|
|
1322
|
+
if (cb.type.kind !== "func" || cb.type.params.length > 0) {
|
|
1323
|
+
L.noLowering("write completion callbacks taking the error argument", cbArg, "() => void callbacks are supported (listen for 'error' to observe failures)");
|
|
1324
|
+
}
|
|
1325
|
+
return { kind: "libCall", fn, args: [receiver, chunk, cb], type: BOOL, loc };
|
|
1326
|
+
}
|
|
1327
|
+
return { kind: "libCall", fn, args: [receiver, chunk], type: BOOL, loc };
|
|
1328
|
+
}
|
|
1329
|
+
if (member === "end") {
|
|
1330
|
+
requireSide(writableSide, "end");
|
|
1331
|
+
if (args.length > 3)
|
|
1332
|
+
L.noLowering(`end with ${args.length} arguments`, call);
|
|
1333
|
+
// Forms: (), (cb), (chunk), (chunk, cb), (chunk, encoding, cb).
|
|
1334
|
+
let chunkNode;
|
|
1335
|
+
let cbArg;
|
|
1336
|
+
if (args.length === 1) {
|
|
1337
|
+
const t = L.mapTypeOf(L.typeOf(args[0]));
|
|
1338
|
+
if (t?.kind === "func")
|
|
1339
|
+
cbArg = args[0];
|
|
1340
|
+
else
|
|
1341
|
+
chunkNode = args[0];
|
|
1342
|
+
}
|
|
1343
|
+
else if (args.length === 2) {
|
|
1344
|
+
chunkNode = args[0];
|
|
1345
|
+
const t = L.mapTypeOf(L.typeOf(args[1]));
|
|
1346
|
+
if (t?.kind === "string")
|
|
1347
|
+
checkUtf8Encoding(L, "end", args[1]);
|
|
1348
|
+
else
|
|
1349
|
+
cbArg = args[1];
|
|
1350
|
+
}
|
|
1351
|
+
else if (args.length === 3) {
|
|
1352
|
+
chunkNode = args[0];
|
|
1353
|
+
checkUtf8Encoding(L, "end", args[1]);
|
|
1354
|
+
cbArg = args[2];
|
|
1355
|
+
}
|
|
1356
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1357
|
+
let flags = 0;
|
|
1358
|
+
const tail = [];
|
|
1359
|
+
if (chunkNode !== undefined) {
|
|
1360
|
+
const kind = chunkKind(chunkNode);
|
|
1361
|
+
if (kind === "null" || kind === "union")
|
|
1362
|
+
L.noLowering("end(null)", call);
|
|
1363
|
+
flags |= kind === "bytes" ? 1 : kind === "dyn" ? 8 : 2;
|
|
1364
|
+
tail.push(kind === "bytes" ? L.lowerExprExpecting(chunkNode, BYTES)
|
|
1365
|
+
: kind === "dyn" ? L.lowerExprExpecting(chunkNode, DYN)
|
|
1366
|
+
: L.lowerExprExpecting(chunkNode, STRING));
|
|
1367
|
+
}
|
|
1368
|
+
if (cbArg !== undefined) {
|
|
1369
|
+
const cb = L.lowerExprExpecting(cbArg, funcOf([], VOID));
|
|
1370
|
+
if (cb.type.kind !== "func" || cb.type.params.length > 0) {
|
|
1371
|
+
L.noLowering("end callbacks with parameters", cbArg, "() => void callbacks are supported");
|
|
1372
|
+
}
|
|
1373
|
+
flags |= 4;
|
|
1374
|
+
tail.push(cb);
|
|
1375
|
+
}
|
|
1376
|
+
return { kind: "libCall", fn: "writable.end", args: [receiver, f64Lit(flags, loc), ...tail], type: receiver.type, loc };
|
|
1377
|
+
}
|
|
1378
|
+
if (member === "cork" || member === "uncork") {
|
|
1379
|
+
requireSide(writableSide, member);
|
|
1380
|
+
if (args.length !== 0)
|
|
1381
|
+
L.noLowering(`${member} with arguments`, call);
|
|
1382
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1383
|
+
return { kind: "libCall", fn: member === "cork" ? "writable.cork" : "writable.uncork", args: [receiver], type: VOID, loc };
|
|
1384
|
+
}
|
|
1385
|
+
if (member === "setEncoding") {
|
|
1386
|
+
requireSide(readableSide, "setEncoding");
|
|
1387
|
+
if (args.length !== 1) {
|
|
1388
|
+
L.noLowering(`setEncoding with ${args.length} arguments`, call, "one literal encoding is the supported form");
|
|
1389
|
+
}
|
|
1390
|
+
const enc = bufEncoding(L, "setEncoding", args[0]);
|
|
1391
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1392
|
+
return { kind: "libCall", fn: "readable.setEncoding", args: [receiver, strLit(enc, loc)], type: receiver.type, loc };
|
|
1393
|
+
}
|
|
1394
|
+
if (member === "setDefaultEncoding") {
|
|
1395
|
+
// The write-side default: utf8 IS the default — admit it; other
|
|
1396
|
+
// encodings would change what write(string) means.
|
|
1397
|
+
requireSide(writableSide, "setDefaultEncoding");
|
|
1398
|
+
const t = args[0] ? L.typeOf(args[0]) : null;
|
|
1399
|
+
if (t?.isStringLiteralType() && (t.value === "utf8" || t.value === "utf-8")) {
|
|
1400
|
+
return L.lowerExpr(access.expression);
|
|
1401
|
+
}
|
|
1402
|
+
L.noLowering("setDefaultEncoding", call, "utf8 (the default, as a compile-time literal) is the supported write-side string encoding");
|
|
1403
|
+
}
|
|
1404
|
+
return null;
|
|
1405
|
+
}
|
|
1406
|
+
/* ── the internal-state read surface (_readableState/_writableState) ──── */
|
|
1407
|
+
/** The `s._readableState.<name>` / `s._writableState.<name>` READS the
|
|
1408
|
+
* suite's asserts use: a read-only compat VIEW over the real runtime
|
|
1409
|
+
* state — each supported name maps onto a state field the runtime
|
|
1410
|
+
* actually keeps, so the answers cannot lie. Writes and the object-
|
|
1411
|
+
* valued members (pipes, awaitDrainWriters, buffered entries) keep
|
|
1412
|
+
* their fences. Node's `ending`/`ended` distinction inside end() and
|
|
1413
|
+
* the per-side errored slots collapse onto the shared fields here
|
|
1414
|
+
* (SEMANTICS.md). */
|
|
1415
|
+
const RS_BOOL_PROPS = new Set([
|
|
1416
|
+
"ended", "endEmitted", "destroyed", "errorEmitted", "emittedReadable",
|
|
1417
|
+
"needReadable", "reading", "readableListening", "resumeScheduled",
|
|
1418
|
+
"objectMode", "constructed", "closed",
|
|
1419
|
+
]);
|
|
1420
|
+
const RS_NUM_PROPS = new Set(["length", "highWaterMark"]);
|
|
1421
|
+
const WS_BOOL_PROPS = new Set([
|
|
1422
|
+
"ended", "ending", "finished", "destroyed", "errorEmitted", "needDrain",
|
|
1423
|
+
"objectMode", "constructed", "closed", "prefinished",
|
|
1424
|
+
]);
|
|
1425
|
+
const WS_NUM_PROPS = new Set([
|
|
1426
|
+
"length", "highWaterMark", "corked", "bufferedRequestCount",
|
|
1427
|
+
]);
|
|
1428
|
+
export function lowerStreamStateProperty(L, expr) {
|
|
1429
|
+
if (expr.questionDotToken)
|
|
1430
|
+
return null;
|
|
1431
|
+
const inner = expr.expression;
|
|
1432
|
+
if (!ts.isPropertyAccessExpression(inner) || inner.questionDotToken)
|
|
1433
|
+
return null;
|
|
1434
|
+
const stateName = inner.name.text;
|
|
1435
|
+
if (stateName !== "_readableState" && stateName !== "_writableState")
|
|
1436
|
+
return null;
|
|
1437
|
+
let recvT;
|
|
1438
|
+
try {
|
|
1439
|
+
recvT = L.mapTypeOf(L.typeOf(inner.expression));
|
|
1440
|
+
}
|
|
1441
|
+
catch {
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
if (recvT?.kind !== "object")
|
|
1445
|
+
return null;
|
|
1446
|
+
const info = L.classes.get(recvT.className);
|
|
1447
|
+
const sides = streamSidesOf(L, info);
|
|
1448
|
+
if (!sides)
|
|
1449
|
+
return null;
|
|
1450
|
+
const loc = locOf(expr);
|
|
1451
|
+
const readable = stateName === "_readableState";
|
|
1452
|
+
if (readable ? sides === "w" : sides === "r") {
|
|
1453
|
+
L.noLowering(`${stateName} on a ${recvT.className.slice(1)} (that half is absent)`, expr);
|
|
1454
|
+
}
|
|
1455
|
+
const name = expr.name.text;
|
|
1456
|
+
const prefix = readable ? "rs:" : "ws:";
|
|
1457
|
+
const receiver = () => L.lowerExpr(inner.expression);
|
|
1458
|
+
if (name === "flowing" && readable) {
|
|
1459
|
+
const type = unionOf(L, [BOOL, { kind: "nullT" }]);
|
|
1460
|
+
const read = { kind: "libCall", fn: "readable.flowing", args: [receiver()], type, loc };
|
|
1461
|
+
return L.maybeNarrow(read, expr);
|
|
1462
|
+
}
|
|
1463
|
+
if (name === "errored") {
|
|
1464
|
+
const type = unionOf(L, [{ kind: "object", className: "%Error" }, { kind: "nullT" }]);
|
|
1465
|
+
const read = { kind: "libCall", fn: "stream.errored", args: [receiver()], type, loc };
|
|
1466
|
+
return L.maybeNarrow(read, expr);
|
|
1467
|
+
}
|
|
1468
|
+
const bools = readable ? RS_BOOL_PROPS : WS_BOOL_PROPS;
|
|
1469
|
+
const nums = readable ? RS_NUM_PROPS : WS_NUM_PROPS;
|
|
1470
|
+
if (bools.has(name) || nums.has(name)) {
|
|
1471
|
+
return {
|
|
1472
|
+
kind: "libCall",
|
|
1473
|
+
fn: "stream.prop",
|
|
1474
|
+
args: [receiver(), strLit(prefix + name, loc)],
|
|
1475
|
+
type: bools.has(name) ? BOOL : F64,
|
|
1476
|
+
loc,
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
L.noLowering(`reading ${stateName}.${name}`, expr, "the lowered internal-state view covers the scalar fields (length, ended, flowing, errorEmitted, ...) — object-valued members like pipes and buffered entries have no lowering");
|
|
1480
|
+
}
|
|
1481
|
+
/* ── the property surface ─────────────────────────────────────────────── */
|
|
1482
|
+
const READABLE_BOOL_PROPS = new Set(["readable", "readableEnded"]);
|
|
1483
|
+
const WRITABLE_BOOL_PROPS = new Set(["writable", "writableEnded", "writableFinished", "writableNeedDrain"]);
|
|
1484
|
+
const SHARED_BOOL_PROPS = new Set(["destroyed", "closed"]);
|
|
1485
|
+
const READABLE_NUM_PROPS = new Set(["readableLength", "readableHighWaterMark"]);
|
|
1486
|
+
const WRITABLE_NUM_PROPS = new Set(["writableLength", "writableHighWaterMark", "writableCorked"]);
|
|
1487
|
+
/** Property reads on stream-rooted receivers (`r.readableEnded`,
|
|
1488
|
+
* `w.destroyed`, ...). Null for names this spoke does not own. */
|
|
1489
|
+
export function lowerStreamProperty(L, expr, info) {
|
|
1490
|
+
const sides = streamSidesOf(L, info);
|
|
1491
|
+
if (!sides)
|
|
1492
|
+
return null;
|
|
1493
|
+
const name = expr.name.text;
|
|
1494
|
+
const loc = locOf(expr);
|
|
1495
|
+
const readableSide = sides === "r" || sides === "rw";
|
|
1496
|
+
const writableSide = sides === "w" || sides === "rw";
|
|
1497
|
+
const propCall = (type) => ({
|
|
1498
|
+
kind: "libCall",
|
|
1499
|
+
fn: "stream.prop",
|
|
1500
|
+
args: [L.lowerExpr(expr.expression), strLit(name, loc)],
|
|
1501
|
+
type,
|
|
1502
|
+
loc,
|
|
1503
|
+
});
|
|
1504
|
+
if ((readableSide && READABLE_BOOL_PROPS.has(name)) || (writableSide && WRITABLE_BOOL_PROPS.has(name)) || SHARED_BOOL_PROPS.has(name)) {
|
|
1505
|
+
return propCall(BOOL);
|
|
1506
|
+
}
|
|
1507
|
+
if ((readableSide && READABLE_NUM_PROPS.has(name)) || (writableSide && WRITABLE_NUM_PROPS.has(name))) {
|
|
1508
|
+
return propCall(F64);
|
|
1509
|
+
}
|
|
1510
|
+
if (name === "readableFlowing" && readableSide) {
|
|
1511
|
+
const type = unionOf(L, [BOOL, { kind: "nullT" }]);
|
|
1512
|
+
const read = { kind: "libCall", fn: "readable.flowing", args: [L.lowerExpr(expr.expression)], type, loc };
|
|
1513
|
+
return L.maybeNarrow(read, expr);
|
|
1514
|
+
}
|
|
1515
|
+
if (name === "errored") {
|
|
1516
|
+
const type = unionOf(L, [{ kind: "object", className: "%Error" }, { kind: "nullT" }]);
|
|
1517
|
+
const read = { kind: "libCall", fn: "stream.errored", args: [L.lowerExpr(expr.expression)], type, loc };
|
|
1518
|
+
return L.maybeNarrow(read, expr);
|
|
1519
|
+
}
|
|
1520
|
+
if (name === "readableObjectMode" && readableSide) {
|
|
1521
|
+
// Runtime state: Readable.from streams answer true (object entries).
|
|
1522
|
+
return propCall(BOOL);
|
|
1523
|
+
}
|
|
1524
|
+
if (name === "writableObjectMode" && writableSide) {
|
|
1525
|
+
// objectMode construction fences, so the answer is compile-time false.
|
|
1526
|
+
return boolLit(false, loc);
|
|
1527
|
+
}
|
|
1528
|
+
if (name === "allowHalfOpen" && sides === "rw") {
|
|
1529
|
+
return propCall(BOOL);
|
|
1530
|
+
}
|
|
1531
|
+
return null;
|
|
1532
|
+
}
|
|
1533
|
+
//# sourceMappingURL=lower-stream.js.map
|