@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.
Files changed (206) hide show
  1. package/LICENSE +202 -0
  2. package/ambient/package.json +3 -0
  3. package/ambient/scriptc-node-fallback.d.ts +3001 -0
  4. package/ambient/scriptc-overrides.d.ts +95 -0
  5. package/ambient/scriptc.d.ts +74 -0
  6. package/dist/backend/cc.d.ts +169 -0
  7. package/dist/backend/cc.js +897 -0
  8. package/dist/backend/cc.js.map +1 -0
  9. package/dist/backend/emission/emit-async.d.ts +145 -0
  10. package/dist/backend/emission/emit-async.js +996 -0
  11. package/dist/backend/emission/emit-async.js.map +1 -0
  12. package/dist/backend/emission/emit-exprs.d.ts +3 -0
  13. package/dist/backend/emission/emit-exprs.js +5949 -0
  14. package/dist/backend/emission/emit-exprs.js.map +1 -0
  15. package/dist/backend/emission/emit-island.d.ts +45 -0
  16. package/dist/backend/emission/emit-island.js +271 -0
  17. package/dist/backend/emission/emit-island.js.map +1 -0
  18. package/dist/backend/emission/emit-shapes.d.ts +142 -0
  19. package/dist/backend/emission/emit-shapes.js +575 -0
  20. package/dist/backend/emission/emit-shapes.js.map +1 -0
  21. package/dist/backend/emission/emit-stmts.d.ts +78 -0
  22. package/dist/backend/emission/emit-stmts.js +960 -0
  23. package/dist/backend/emission/emit-stmts.js.map +1 -0
  24. package/dist/backend/emission/emit-types.d.ts +65 -0
  25. package/dist/backend/emission/emit-types.js +652 -0
  26. package/dist/backend/emission/emit-types.js.map +1 -0
  27. package/dist/backend/emission/emit-walkers.d.ts +154 -0
  28. package/dist/backend/emission/emit-walkers.js +1587 -0
  29. package/dist/backend/emission/emit-walkers.js.map +1 -0
  30. package/dist/backend/emission/emitter.d.ts +452 -0
  31. package/dist/backend/emission/emitter.js +1249 -0
  32. package/dist/backend/emission/emitter.js.map +1 -0
  33. package/dist/backend/emission/may-throw.d.ts +15 -0
  34. package/dist/backend/emission/may-throw.js +250 -0
  35. package/dist/backend/emission/may-throw.js.map +1 -0
  36. package/dist/backend/llvm/blocks.d.ts +21 -0
  37. package/dist/backend/llvm/blocks.js +68 -0
  38. package/dist/backend/llvm/blocks.js.map +1 -0
  39. package/dist/backend/llvm/classes.d.ts +84 -0
  40. package/dist/backend/llvm/classes.js +438 -0
  41. package/dist/backend/llvm/classes.js.map +1 -0
  42. package/dist/backend/llvm/dyn.d.ts +123 -0
  43. package/dist/backend/llvm/dyn.js +2624 -0
  44. package/dist/backend/llvm/dyn.js.map +1 -0
  45. package/dist/backend/llvm/emitter.d.ts +3 -0
  46. package/dist/backend/llvm/emitter.js +10102 -0
  47. package/dist/backend/llvm/emitter.js.map +1 -0
  48. package/dist/backend/llvm/shapes.d.ts +78 -0
  49. package/dist/backend/llvm/shapes.js +754 -0
  50. package/dist/backend/llvm/shapes.js.map +1 -0
  51. package/dist/backend/llvm/unsupported.d.ts +6 -0
  52. package/dist/backend/llvm/unsupported.js +12 -0
  53. package/dist/backend/llvm/unsupported.js.map +1 -0
  54. package/dist/backend/llvm/walkers.d.ts +58 -0
  55. package/dist/backend/llvm/walkers.js +793 -0
  56. package/dist/backend/llvm/walkers.js.map +1 -0
  57. package/dist/backend/mangle.d.ts +124 -0
  58. package/dist/backend/mangle.js +232 -0
  59. package/dist/backend/mangle.js.map +1 -0
  60. package/dist/coverage/report.d.ts +65 -0
  61. package/dist/coverage/report.js +238 -0
  62. package/dist/coverage/report.js.map +1 -0
  63. package/dist/diagnostics/diagnostic.d.ts +140 -0
  64. package/dist/diagnostics/diagnostic.js +458 -0
  65. package/dist/diagnostics/diagnostic.js.map +1 -0
  66. package/dist/diagnostics/render.d.ts +11 -0
  67. package/dist/diagnostics/render.js +58 -0
  68. package/dist/diagnostics/render.js.map +1 -0
  69. package/dist/frontend/cjs-lexer.d.ts +20 -0
  70. package/dist/frontend/cjs-lexer.js +813 -0
  71. package/dist/frontend/cjs-lexer.js.map +1 -0
  72. package/dist/frontend/lowering/http2-constants.d.ts +1 -0
  73. package/dist/frontend/lowering/http2-constants.js +251 -0
  74. package/dist/frontend/lowering/http2-constants.js.map +1 -0
  75. package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
  76. package/dist/frontend/lowering/lib-boundary.js +143 -0
  77. package/dist/frontend/lowering/lib-boundary.js.map +1 -0
  78. package/dist/frontend/lowering/lower-assert.d.ts +18 -0
  79. package/dist/frontend/lowering/lower-assert.js +1269 -0
  80. package/dist/frontend/lowering/lower-assert.js.map +1 -0
  81. package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
  82. package/dist/frontend/lowering/lower-builtins.js +5249 -0
  83. package/dist/frontend/lowering/lower-builtins.js.map +1 -0
  84. package/dist/frontend/lowering/lower-calls.d.ts +581 -0
  85. package/dist/frontend/lowering/lower-calls.js +6286 -0
  86. package/dist/frontend/lowering/lower-calls.js.map +1 -0
  87. package/dist/frontend/lowering/lower-classes.d.ts +727 -0
  88. package/dist/frontend/lowering/lower-classes.js +4701 -0
  89. package/dist/frontend/lowering/lower-classes.js.map +1 -0
  90. package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
  91. package/dist/frontend/lowering/lower-comptime.js +244 -0
  92. package/dist/frontend/lowering/lower-comptime.js.map +1 -0
  93. package/dist/frontend/lowering/lower-containers.d.ts +486 -0
  94. package/dist/frontend/lowering/lower-containers.js +6345 -0
  95. package/dist/frontend/lowering/lower-containers.js.map +1 -0
  96. package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
  97. package/dist/frontend/lowering/lower-dgram.js +319 -0
  98. package/dist/frontend/lowering/lower-dgram.js.map +1 -0
  99. package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
  100. package/dist/frontend/lowering/lower-emitter.js +688 -0
  101. package/dist/frontend/lowering/lower-emitter.js.map +1 -0
  102. package/dist/frontend/lowering/lower-enums.d.ts +22 -0
  103. package/dist/frontend/lowering/lower-enums.js +235 -0
  104. package/dist/frontend/lowering/lower-enums.js.map +1 -0
  105. package/dist/frontend/lowering/lower-expando.d.ts +35 -0
  106. package/dist/frontend/lowering/lower-expando.js +276 -0
  107. package/dist/frontend/lowering/lower-expando.js.map +1 -0
  108. package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
  109. package/dist/frontend/lowering/lower-exprs.js +8111 -0
  110. package/dist/frontend/lowering/lower-exprs.js.map +1 -0
  111. package/dist/frontend/lowering/lower-generators.d.ts +50 -0
  112. package/dist/frontend/lowering/lower-generators.js +421 -0
  113. package/dist/frontend/lowering/lower-generators.js.map +1 -0
  114. package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
  115. package/dist/frontend/lowering/lower-inspect.js +1074 -0
  116. package/dist/frontend/lowering/lower-inspect.js.map +1 -0
  117. package/dist/frontend/lowering/lower-island.d.ts +108 -0
  118. package/dist/frontend/lowering/lower-island.js +650 -0
  119. package/dist/frontend/lowering/lower-island.js.map +1 -0
  120. package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
  121. package/dist/frontend/lowering/lower-mixins.js +534 -0
  122. package/dist/frontend/lowering/lower-mixins.js.map +1 -0
  123. package/dist/frontend/lowering/lower-modules.d.ts +135 -0
  124. package/dist/frontend/lowering/lower-modules.js +1613 -0
  125. package/dist/frontend/lowering/lower-modules.js.map +1 -0
  126. package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
  127. package/dist/frontend/lowering/lower-namespaces.js +706 -0
  128. package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
  129. package/dist/frontend/lowering/lower-server.d.ts +90 -0
  130. package/dist/frontend/lowering/lower-server.js +3471 -0
  131. package/dist/frontend/lowering/lower-server.js.map +1 -0
  132. package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
  133. package/dist/frontend/lowering/lower-stmts.js +5341 -0
  134. package/dist/frontend/lowering/lower-stmts.js.map +1 -0
  135. package/dist/frontend/lowering/lower-stream.d.ts +88 -0
  136. package/dist/frontend/lowering/lower-stream.js +1533 -0
  137. package/dist/frontend/lowering/lower-stream.js.map +1 -0
  138. package/dist/frontend/lowering/lower-test.d.ts +27 -0
  139. package/dist/frontend/lowering/lower-test.js +353 -0
  140. package/dist/frontend/lowering/lower-test.js.map +1 -0
  141. package/dist/frontend/lowering/lowerer.d.ts +1748 -0
  142. package/dist/frontend/lowering/lowerer.js +6257 -0
  143. package/dist/frontend/lowering/lowerer.js.map +1 -0
  144. package/dist/frontend/lowering/surfaces.d.ts +272 -0
  145. package/dist/frontend/lowering/surfaces.js +1096 -0
  146. package/dist/frontend/lowering/surfaces.js.map +1 -0
  147. package/dist/frontend/npm-static.d.ts +55 -0
  148. package/dist/frontend/npm-static.js +300 -0
  149. package/dist/frontend/npm-static.js.map +1 -0
  150. package/dist/frontend/npm.d.ts +330 -0
  151. package/dist/frontend/npm.js +1259 -0
  152. package/dist/frontend/npm.js.map +1 -0
  153. package/dist/frontend/program.d.ts +186 -0
  154. package/dist/frontend/program.js +2255 -0
  155. package/dist/frontend/program.js.map +1 -0
  156. package/dist/frontend/provenance-registry.d.ts +48 -0
  157. package/dist/frontend/provenance-registry.js +87 -0
  158. package/dist/frontend/provenance-registry.js.map +1 -0
  159. package/dist/frontend/provenance.d.ts +6 -0
  160. package/dist/frontend/provenance.js +459 -0
  161. package/dist/frontend/provenance.js.map +1 -0
  162. package/dist/frontend/resolve.d.ts +56 -0
  163. package/dist/frontend/resolve.js +682 -0
  164. package/dist/frontend/resolve.js.map +1 -0
  165. package/dist/frontend/shared.d.ts +67 -0
  166. package/dist/frontend/shared.js +241 -0
  167. package/dist/frontend/shared.js.map +1 -0
  168. package/dist/frontend/ts7/adapter.d.ts +7 -0
  169. package/dist/frontend/ts7/adapter.js +54 -0
  170. package/dist/frontend/ts7/adapter.js.map +1 -0
  171. package/dist/frontend/ts7/ast.d.ts +50 -0
  172. package/dist/frontend/ts7/ast.js +211 -0
  173. package/dist/frontend/ts7/ast.js.map +1 -0
  174. package/dist/frontend/ts7/census-check.d.ts +1 -0
  175. package/dist/frontend/ts7/census-check.js +12 -0
  176. package/dist/frontend/ts7/census-check.js.map +1 -0
  177. package/dist/frontend/ts7/checker.d.ts +140 -0
  178. package/dist/frontend/ts7/checker.js +544 -0
  179. package/dist/frontend/ts7/checker.js.map +1 -0
  180. package/dist/frontend/ts7/enums.d.ts +18 -0
  181. package/dist/frontend/ts7/enums.js +43 -0
  182. package/dist/frontend/ts7/enums.js.map +1 -0
  183. package/dist/frontend/ts7/program.d.ts +99 -0
  184. package/dist/frontend/ts7/program.js +278 -0
  185. package/dist/frontend/ts7/program.js.map +1 -0
  186. package/dist/frontend/ts7/world-check.d.ts +3 -0
  187. package/dist/frontend/ts7/world-check.js +34 -0
  188. package/dist/frontend/ts7/world-check.js.map +1 -0
  189. package/dist/frontend/types.d.ts +205 -0
  190. package/dist/frontend/types.js +2485 -0
  191. package/dist/frontend/types.js.map +1 -0
  192. package/dist/index.d.ts +85 -0
  193. package/dist/index.js +429 -0
  194. package/dist/index.js.map +1 -0
  195. package/dist/ir/nodes.d.ts +4334 -0
  196. package/dist/ir/nodes.js +1906 -0
  197. package/dist/ir/nodes.js.map +1 -0
  198. package/dist/ir/serialize.d.ts +4 -0
  199. package/dist/ir/serialize.js +34 -0
  200. package/dist/ir/serialize.js.map +1 -0
  201. package/dist/ir/validate.d.ts +36 -0
  202. package/dist/ir/validate.js +4885 -0
  203. package/dist/ir/validate.js.map +1 -0
  204. package/package.json +30 -6
  205. package/README.md +0 -3
  206. 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