@scriptc/compiler 0.0.0 → 0.0.1

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 +5947 -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 +10087 -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 +64 -0
  61. package/dist/coverage/report.js +226 -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 +6281 -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 +483 -0
  94. package/dist/frontend/lowering/lower-containers.js +6281 -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 +8099 -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 +1069 -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 +6256 -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 +2172 -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 +60 -0
  166. package/dist/frontend/shared.js +232 -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 +424 -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 +4884 -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,3471 @@
1
+ /* The server-surface lowering (node:net + node:http — the spoke-module
2
+ * pattern, like lower-island.ts): module-function calls (net
3
+ * createServer/connect/createConnection, http createServer/request/get —
4
+ * named AND namespace import forms, both dispatched here), method calls
5
+ * on netServer/netSocket/httpReq/httpRes/httpClientReq receivers, the
6
+ * composed `server.address().port` read, and the property surface
7
+ * (req.url/method/statusCode/socket/headers, res.headersSent,
8
+ * socket.remoteAddress, request.destroyed). Everything the lib declares
9
+ * beyond these shapes fences member-qualified — never a generic
10
+ * rejection, never silence. */
11
+ import * as ts from "../ts7/adapter.js";
12
+ import { locOf } from "../program.js";
13
+ import { arrayOf, BOOL, BYTES_U8, canBoxFuncIntoDyn, canConvertToDyn, DYN, DYN_HANDLE_KINDS, F64, funcOf, HTTP2SESSION_T, HTTP2STREAM_T, HTTPCLIENTREQ_T, HTTPREQ_T, HTTPRES_T, NETSERVER_T, NETSOCKET_T, NULL_T, SECURECTX_T, STRING, UNDEFINED_T, typeKey, VOID } from "../../ir/nodes.js";
14
+ import { builtinFenceHintOf, builtinModuleFnOf, fenceOrDropOptionKey, HTTP_CLIENT_DOCUMENTED_OPTIONS, HTTP_SERVER_DOCUMENTED_OPTIONS, HTTP2_SECURE_SERVER_DOCUMENTED_OPTIONS, HTTPS_CLIENT_DOCUMENTED_OPTIONS, sideEffectFreeOptionValue, TLS_SERVER_DOCUMENTED_OPTIONS, } from "./surfaces.js";
15
+ import { conditionalSpreadOf } from "./lower-exprs.js";
16
+ /** The lowered value members of the net module — the surfaces.ts table's
17
+ * spoke-side twin (the call SHAPES are all special-cased here, so the
18
+ * entries carry no param/result rows). */
19
+ export const NET_MODULE_FNS = new Set([
20
+ "createServer",
21
+ "connect",
22
+ "createConnection",
23
+ ]);
24
+ const NARROW_DATA_HINT = 'write/end take one string or one Uint8Array/Buffer value (narrow unions first)';
25
+ /** VOID-result server/socket calls are usable as statements and as concise
26
+ * arrow bodies (`socket.on("error", () => socket.destroy())` — the shape
27
+ * portless is made of); anything that would CONSUME the result (Node
28
+ * returns `this`/a boolean where this surface returns void) is fenced. */
29
+ function requireStatementPosition(L, call, what) {
30
+ if (ts.isExpressionStatement(call.parent) || ts.isArrowFunction(call.parent))
31
+ return;
32
+ L.unsupported("SC1090", call, `using the result of ${what} (the result is void here — call it as its own statement)`);
33
+ }
34
+ /** The %Error param shape the error-listener slots carry. */
35
+ const ERROR_T = { kind: "object", className: "%Error" };
36
+ /** Node ignores listener/handler return values. A value-returning
37
+ * callback rides an interned cast-and-discard adapter (the stream lane's
38
+ * bare-expression-body discard, generalized to closure VALUES): same
39
+ * parameters, void return, the underlying result discarded unread. */
40
+ function voidizedCallback(L, cb, loc) {
41
+ if (cb.type.kind !== "func" || cb.type.ret.kind === "void")
42
+ return cb;
43
+ const fromT = cb.type;
44
+ const toT = funcOf([...fromT.params], VOID);
45
+ const key = `server.voidcb:${typeKey(fromT)}`;
46
+ const existing = L.arrHofHelpers.get(key);
47
+ const name = existing ?? `%server.voidcb.${L.arrHofHelpers.size}`;
48
+ if (!existing) {
49
+ L.arrHofHelpers.set(key, name);
50
+ const impl = `${name}.impl`;
51
+ const params = fromT.params.map((p, i) => ({ localId: `p${i}.0`, name: `p${i}`, type: p }));
52
+ L.liftedFns.push({
53
+ name: impl,
54
+ params,
55
+ returnType: VOID,
56
+ captures: [{ localId: "f.0", name: "f", type: fromT }],
57
+ locals: [
58
+ { id: "f.0", name: "f", type: fromT, mutable: false, boxed: true },
59
+ ...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
60
+ ],
61
+ body: [
62
+ {
63
+ kind: "exprStmt",
64
+ expr: {
65
+ kind: "callValue",
66
+ callee: { kind: "varRef", localId: "f.0", type: fromT, loc },
67
+ args: params.map((p) => ({ kind: "varRef", localId: p.localId, type: p.type, loc })),
68
+ type: fromT.ret,
69
+ loc,
70
+ },
71
+ loc,
72
+ },
73
+ ],
74
+ loc,
75
+ });
76
+ L.liftedFns.push({
77
+ name,
78
+ params: [{ localId: "f.0", name: "f", type: fromT }],
79
+ returnType: toT,
80
+ locals: [{ id: "f.0", name: "f", type: fromT, mutable: false, boxed: true }],
81
+ body: [{ kind: "return", value: { kind: "closure", fnName: impl, captures: ["f.0"], type: toT, loc }, loc }],
82
+ loc,
83
+ });
84
+ }
85
+ return { kind: "call", callee: name, args: [cb], type: toT, loc };
86
+ }
87
+ /** A registration/side-effect libCall re-shaped to answer its RECEIVER —
88
+ * Node's `return this` chaining (server.listen(...), res.writeHead(...)):
89
+ * an interned helper runs the void call and returns its first argument.
90
+ * `prefix` calls (the writeHead statusMessage form's resStatusMsgSet)
91
+ * run first, sharing the same parameter refs. */
92
+ function receiverReturningCall(L, fn, callArgs, recvT, loc, opts) {
93
+ const key = `server.recvret:${fn}:${opts?.prefix?.fn ?? ""}:${(opts?.mainArgIndices ?? []).join(".")}:${callArgs.map((a) => typeKey(a.type)).join(",")}`;
94
+ const existing = L.arrHofHelpers.get(key);
95
+ const name = existing ?? `%server.recvret.${L.arrHofHelpers.size}`;
96
+ if (!existing) {
97
+ L.arrHofHelpers.set(key, name);
98
+ const params = callArgs.map((a, i) => ({ localId: `p${i}.0`, name: `p${i}`, type: a.type }));
99
+ const ref = (i) => ({ kind: "varRef", localId: params[i].localId, type: params[i].type, loc });
100
+ const body = [];
101
+ if (opts?.prefix) {
102
+ body.push({
103
+ kind: "exprStmt",
104
+ expr: { kind: "libCall", fn: opts.prefix.fn, args: opts.prefix.argIndices.map(ref), type: VOID, loc },
105
+ loc,
106
+ });
107
+ }
108
+ const mainIdx = opts?.mainArgIndices ?? params.map((_, i) => i);
109
+ body.push({
110
+ kind: "exprStmt",
111
+ expr: { kind: "libCall", fn, args: mainIdx.map(ref), type: VOID, loc },
112
+ loc,
113
+ });
114
+ body.push({ kind: "return", value: ref(0), loc });
115
+ L.liftedFns.push({
116
+ name,
117
+ params,
118
+ returnType: recvT,
119
+ locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
120
+ body,
121
+ loc,
122
+ });
123
+ }
124
+ return { kind: "call", callee: name, args: callArgs, type: recvT, loc };
125
+ }
126
+ /** Lowers a listener/callback argument, pinning the exact closure shape:
127
+ * at most `maxParams` parameters and (when present) the parameter's IR
128
+ * kind must satisfy `paramOk`. A value-returning closure adapts through
129
+ * the cast-and-discard helper (Node ignores listener results). When
130
+ * `dynTuple` is given (the event's payload types, every one
131
+ * dyn-convertible) a CHECKED-DYNAMIC callback — a JS wrapper value like
132
+ * test/common's mustCall result — adapts through the dynCheck function
133
+ * boundary instead of fencing; handle-carrying events pass no tuple and
134
+ * keep their fences. Returns the lowered closure and its param count. */
135
+ function lowerCallbackArg(L, node, what, maxParams, paramOk, paramHint, dynTuple) {
136
+ const cb = L.lowerExpr(node);
137
+ if (cb.type.kind === "dyn" && dynTuple !== undefined) {
138
+ const toT = funcOf([...dynTuple], VOID);
139
+ return {
140
+ cb: { kind: "dynCheck", value: cb, type: toT, loc: locOf(node) },
141
+ nparams: dynTuple.length,
142
+ };
143
+ }
144
+ if (dynTuple !== undefined &&
145
+ cb.type.kind === "func" &&
146
+ (cb.type.rest === true ||
147
+ (cb.type.params.length > 0 && cb.type.params.some((p) => !paramOk(p)))) &&
148
+ cb.type.params.every((p) => p.kind === "dyn") &&
149
+ canBoxFuncIntoDyn(cb.type, (id) => L.shapes.get(id), (id) => L.unions.get(id))) {
150
+ // A plain-JS listener whose params carry no contextual type (a
151
+ // hoisted `function onConn(socket)` — dyn params), or an
152
+ // arguments-reading rest function: box it and adapt through the
153
+ // dynCheck function boundary like the dyn flavor above — the event
154
+ // payloads box into the DOM (handles by reference) and the body's
155
+ // member uses dispatch at runtime.
156
+ const boxed = { kind: "dynFrom", value: cb, type: DYN, loc: locOf(node) };
157
+ const toT = funcOf([...dynTuple], VOID);
158
+ return {
159
+ cb: { kind: "dynCheck", value: boxed, type: toT, loc: locOf(node) },
160
+ nparams: dynTuple.length,
161
+ };
162
+ }
163
+ if (cb.type.kind !== "func" || cb.type.params.length > maxParams) {
164
+ L.unsupported("SC1090", node, `${what} with more than ${maxParams} parameter${maxParams === 1 ? "" : "s"} (${paramHint})`);
165
+ }
166
+ const param = cb.type.params[0];
167
+ if (param !== undefined && !paramOk(param)) {
168
+ L.unsupported("SC1090", node, `${what} whose parameter is not supported (${paramHint})`);
169
+ }
170
+ const adapted = voidizedCallback(L, cb, locOf(node));
171
+ return { cb: adapted, nparams: cb.type.params.length };
172
+ }
173
+ const boolLit = (value, loc) => ({ kind: "boolLit", value, type: BOOL, loc });
174
+ /** Lowers a handle-kind method receiver. The checker's control flow can
175
+ * narrow an untyped binding to the handle class (`let server; server =
176
+ * createServer(...)` — the keep-alive shape) so the STATIC lowering
177
+ * claims the call while the binding's IR type is dyn: the receiver then
178
+ * rides a dynCheck unwrap (a tag-checked reference extraction, identity
179
+ * preserved — DYN_HANDLE_KINDS) onto the same entry points. */
180
+ /** True iff `t` is the {address: string, family: string, port: number}
181
+ * record — server.address()'s materialized shape (lower-dgram's
182
+ * AddressInfo check, another receiver). */
183
+ function isAddressInfoRecord(L, t) {
184
+ if (t.kind !== "record")
185
+ return false;
186
+ const shape = L.shapes.get(t.shapeId);
187
+ if (!shape || shape.tuple || shape.indexValue || shape.fields.length !== 3)
188
+ return false;
189
+ const want = [["address", "string"], ["family", "string"], ["port", "f64"]];
190
+ return shape.fields.every((f, i) => f.name === want[i][0] && f.type.kind === want[i][1]);
191
+ }
192
+ function handleReceiver(L, node, want) {
193
+ const recv = L.lowerExpr(node);
194
+ if (recv.type.kind === "dyn" && DYN_HANDLE_KINDS.has(want.kind)) {
195
+ return { kind: "dynCheck", value: recv, type: want, loc: locOf(node) };
196
+ }
197
+ return recv;
198
+ }
199
+ /** Statements that build a header record `out` (a pre-declared local of
200
+ * the pure-index record `shapeId`) from a flat [name, value, ...] pairs
201
+ * local: every value wraps at the slot's string arm, and — when
202
+ * `statusLocal` is given — the ":status" key is overwritten with the f64
203
+ * arm (the response record's numeric :status, the byte-exact assert
204
+ * target). Mirrors headersSnapshotHelper's loop, sourced from a pairs
205
+ * array instead of reqHeaderPairs. Null when the shape is not a
206
+ * string-armed pure-index record. */
207
+ function h2HeaderRecordStmts(L, shapeId, pairsLocal, outLocal, statusLocal, loc) {
208
+ const shape = L.shapes.get(shapeId);
209
+ if (!shape || shape.tuple || shape.fields.length > 0 || !shape.indexValue)
210
+ return null;
211
+ const iv = shape.indexValue;
212
+ if (iv.kind !== "union")
213
+ return null;
214
+ const strTag = L.armTag(iv.unionId, STRING);
215
+ if (strTag < 0)
216
+ return null;
217
+ const recT = { kind: "record", shapeId };
218
+ const pairsT = arrayOf(STRING);
219
+ const ref = (localId, type) => ({ kind: "varRef", localId, type, loc });
220
+ const num = (value) => ({ kind: "numLit", value, type: F64, loc });
221
+ const pairAt = (offset) => ({
222
+ kind: "arrayGet",
223
+ arr: ref(pairsLocal, pairsT),
224
+ index: offset === 0 ? ref("i.0", F64) : { kind: "bin", op: "+", left: ref("i.0", F64), right: num(offset), type: F64, loc },
225
+ type: STRING,
226
+ loc,
227
+ });
228
+ const stmts = [
229
+ { kind: "varDecl", localId: outLocal, init: { kind: "recordLit", fields: [], type: recT, loc }, loc },
230
+ {
231
+ kind: "for",
232
+ init: { kind: "varDecl", localId: "i.0", init: num(0), loc },
233
+ cond: {
234
+ kind: "bin", op: "<", left: ref("i.0", F64),
235
+ right: { kind: "arrIntrinsic", method: "length", receiver: ref(pairsLocal, pairsT), args: [], type: F64, loc },
236
+ type: BOOL, loc,
237
+ },
238
+ update: { kind: "assign", localId: "i.0", value: { kind: "bin", op: "+", left: ref("i.0", F64), right: num(2), type: F64, loc }, loc },
239
+ body: [{
240
+ kind: "recordKeySet", obj: ref(outLocal, recT), shapeId, key: pairAt(0),
241
+ value: { kind: "unionWrap", unionId: iv.unionId, tag: strTag, value: pairAt(1), type: iv, loc }, loc,
242
+ }],
243
+ loc,
244
+ },
245
+ ];
246
+ if (statusLocal !== null) {
247
+ const f64Tag = L.armTag(iv.unionId, F64);
248
+ if (f64Tag < 0)
249
+ return null;
250
+ stmts.push({
251
+ kind: "recordKeySet", obj: ref(outLocal, recT), shapeId,
252
+ key: { kind: "strLit", value: ":status", type: STRING, loc },
253
+ value: { kind: "unionWrap", unionId: iv.unionId, tag: f64Tag, value: ref(statusLocal, F64), type: iv, loc }, loc,
254
+ });
255
+ }
256
+ return stmts;
257
+ }
258
+ /** The record shapeId of a callback parameter that must be the canonical
259
+ * header record (a pure-index string-armed record). Null when the param
260
+ * is absent or not such a record. */
261
+ function headerParamShapeId(cbT, idx) {
262
+ if (cbT.kind !== "func")
263
+ return null;
264
+ const p = cbT.params[idx];
265
+ if (!p || p.kind !== "record")
266
+ return null;
267
+ return p.shapeId;
268
+ }
269
+ /** Adapts a user 'stream'/'response' listener to the runtime thunk ABI,
270
+ * building the header record from the pairs the runtime hands over. The
271
+ * runtime ABI's leading param is the STREAM handle (kind === handleKind,
272
+ * or absent for 'response'); a trailing f64 status feeds the record's
273
+ * :status when `withStatus`. Returns the adapter closure (VOID-returning,
274
+ * runtime ABI) to pass into the libCall. */
275
+ /** The canonical header record type — a pure-index record over the
276
+ * header slot `f64 | string | string[] | undefined` (types.ts's
277
+ * HEADER-FAMILY canonicalization, rebuilt here for dyn-callback headers
278
+ * with no static param type to read the shape from). */
279
+ function canonicalHeaderRecord(L) {
280
+ const arms = [F64, STRING, arrayOf(STRING), UNDEFINED_T];
281
+ arms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
282
+ const slot = { kind: "union", unionId: L.unions.intern(arms) };
283
+ const shapeId = L.shapes.intern([], false, slot, []);
284
+ return { type: { kind: "record", shapeId }, shapeId };
285
+ }
286
+ function h2HeadersCallbackAdapter(L, node, what, handleKind, withStatus, loc) {
287
+ let cb = L.lowerExpr(node);
288
+ const maxParams = (handleKind ? 1 : 0) + 1 /* headers */ + 1 /* flags */;
289
+ // A checked-dynamic listener (test/common's mustCall wrapper — a dyn
290
+ // value or an arguments-reading rest function of dyn params): wrap it
291
+ // in a dynCheck to the CANONICAL event signature, then build the header
292
+ // record and invoke through that typed boundary (handles box by
293
+ // reference, the record boxes to a DOM object — the net server-callbacks
294
+ // precedent). The synthesized record IS the shape the adapter builds.
295
+ const handleT = handleKind === "http2Stream" ? HTTP2STREAM_T : null;
296
+ const isDynCb = cb.type.kind === "dyn" ||
297
+ (cb.type.kind === "func" && (cb.type.rest === true || cb.type.params.every((p) => p.kind === "dyn")) &&
298
+ canBoxFuncIntoDyn(cb.type, (id) => L.shapes.get(id), (id) => L.unions.get(id)));
299
+ if (isDynCb) {
300
+ const rec = canonicalHeaderRecord(L);
301
+ const tuple = [];
302
+ if (handleT)
303
+ tuple.push(handleT);
304
+ tuple.push(rec.type);
305
+ tuple.push(F64);
306
+ const toT = funcOf(tuple, VOID);
307
+ const boxed = cb.type.kind === "dyn" ? cb : { kind: "dynFrom", value: cb, type: DYN, loc };
308
+ cb = { kind: "dynCheck", value: boxed, type: toT, loc };
309
+ }
310
+ if (cb.type.kind !== "func" || cb.type.ret.kind !== "void" || cb.type.params.length > maxParams) {
311
+ L.unsupported("SC1090", node, `${what} with more than ${maxParams} parameters or returning a value`);
312
+ }
313
+ const fromT = cb.type;
314
+ const headersIdx = handleT ? 1 : 0;
315
+ const shapeId = fromT.params.length > headersIdx ? headerParamShapeId(fromT, headersIdx) : null;
316
+ if (fromT.params.length > headersIdx && shapeId === null) {
317
+ L.unsupported("SC1090", node, `${what} whose headers parameter is not a header record (type it IncomingHttpHeaders)`);
318
+ }
319
+ // Runtime ABI params, in order.
320
+ const abiParams = [];
321
+ if (handleT)
322
+ abiParams.push({ localId: "h.0", name: "h", type: handleT });
323
+ abiParams.push({ localId: "ps.0", name: "ps", type: arrayOf(STRING) });
324
+ if (withStatus)
325
+ abiParams.push({ localId: "st.0", name: "st", type: F64 });
326
+ abiParams.push({ localId: "fl.0", name: "fl", type: F64 });
327
+ const toT = funcOf(abiParams.map((p) => p.type), VOID);
328
+ const key = `h2.hdrcb:${handleKind}:${withStatus}:${typeKey(fromT)}:${shapeId ?? ""}`;
329
+ const existing = L.arrHofHelpers.get(key);
330
+ const name = existing ?? `%h2.hdrcb.${L.arrHofHelpers.size}`;
331
+ if (!existing) {
332
+ L.arrHofHelpers.set(key, name);
333
+ const impl = `${name}.impl`;
334
+ const ref = (localId, type) => ({ kind: "varRef", localId, type, loc });
335
+ const locals = [
336
+ { id: "f.0", name: "f", type: fromT, mutable: false, boxed: true },
337
+ ...abiParams.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
338
+ ];
339
+ const body = [];
340
+ let recordRef = null;
341
+ if (shapeId !== null) {
342
+ locals.push({ id: "out.0", name: "out", type: { kind: "record", shapeId }, mutable: false });
343
+ locals.push({ id: "i.0", name: "i", type: F64, mutable: true });
344
+ const stmts = h2HeaderRecordStmts(L, shapeId, "ps.0", "out.0", withStatus ? "st.0" : null, loc);
345
+ if (stmts === null)
346
+ L.unsupported("SC1090", node, `${what} whose headers parameter is not a supported header record`);
347
+ body.push(...stmts);
348
+ recordRef = ref("out.0", { kind: "record", shapeId });
349
+ }
350
+ // Assemble the user-call arguments in the user's declared order.
351
+ const callArgs = [];
352
+ const push = (e) => { if (callArgs.length < fromT.params.length)
353
+ callArgs.push(e); };
354
+ if (handleT)
355
+ push(ref("h.0", handleT));
356
+ if (recordRef)
357
+ push(recordRef);
358
+ else if (fromT.params.length > headersIdx)
359
+ push(ref("ps.0", arrayOf(STRING)));
360
+ push(ref("fl.0", F64));
361
+ body.push({
362
+ kind: "exprStmt",
363
+ expr: { kind: "callValue", callee: ref("f.0", fromT), args: callArgs.slice(0, fromT.params.length), type: fromT.ret, loc },
364
+ loc,
365
+ });
366
+ L.liftedFns.push({ name: impl, params: abiParams, returnType: VOID, captures: [{ localId: "f.0", name: "f", type: fromT }], locals, body, loc });
367
+ L.liftedFns.push({
368
+ name, params: [{ localId: "f.0", name: "f", type: fromT }], returnType: toT,
369
+ locals: [{ id: "f.0", name: "f", type: fromT, mutable: false, boxed: true }],
370
+ body: [{ kind: "return", value: { kind: "closure", fnName: impl, captures: ["f.0"], type: toT, loc }, loc }], loc,
371
+ });
372
+ }
373
+ return { kind: "call", callee: name, args: [cb], type: toT, loc };
374
+ }
375
+ /** Builds the flat [name, value, ...] pairs array from an OutgoingHttp
376
+ * Headers argument (an object literal with literal/pseudo keys, or a
377
+ * header Record). Reuses lowerClientHeadersOption's shape. */
378
+ function lowerH2HeadersArg(L, node) {
379
+ return lowerClientHeadersOption(L, node);
380
+ }
381
+ /** The STATICALLY-KNOWN key text of a header-object property: literal
382
+ * identifier/string keys, plus COMPUTED keys that are a bare identifier
383
+ * of string-literal type — `[PORTLESS_HEADER]: "1"` over a module const
384
+ * (the portless proxy shape) is as static as spelling the name out, and
385
+ * an identifier read has no side effects to drop. Null for genuinely
386
+ * dynamic keys (the caller fences). */
387
+ function staticHeaderKeyOf(L, prop) {
388
+ if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))
389
+ return prop.name.text;
390
+ if (ts.isComputedPropertyName(prop.name) && ts.isIdentifier(prop.name.expression)) {
391
+ const t = L.typeOf(prop.name.expression);
392
+ if (t.isStringLiteralType())
393
+ return t.value;
394
+ }
395
+ return null;
396
+ }
397
+ /** Module-function calls on net import bindings: createServer(handler?),
398
+ * connect/createConnection(port[, host][, cb]). Null for other modules
399
+ * (the caller falls through to the ordinary builtin tables); every net
400
+ * member lands here — unlowered ones fence with their module-qualified
401
+ * name. */
402
+ export function lowerNetModuleCall(L, expr, bi, loc) {
403
+ if (bi.module === "http")
404
+ return lowerHttpModuleCall(L, expr, bi, loc);
405
+ if (bi.module === "https")
406
+ return lowerHttpsModuleCall(L, expr, bi, loc);
407
+ if (bi.module === "tls")
408
+ return lowerTlsModuleCall(L, expr, bi, loc);
409
+ if (bi.module === "http2")
410
+ return lowerHttp2ModuleCall(L, expr, bi, loc);
411
+ if (bi.module !== "net")
412
+ return null;
413
+ // Members with ordinary surfaces.ts table entries (the autoselect
414
+ // budget pair) ride the generic path — null hands the dispatch back.
415
+ if (builtinModuleFnOf(L, "net", bi.member))
416
+ return null;
417
+ const args = expr.arguments;
418
+ if (bi.member === "createServer") {
419
+ if (args.length === 0) {
420
+ return { kind: "libCall", fn: "net.createServer", args: [], type: NETSERVER_T, loc };
421
+ }
422
+ if (args.length === 1) {
423
+ const { cb } = lowerCallbackArg(L, args[0], "connection handlers", 1, (p) => p.kind === "netSocket", "use (socket) or ()", [NETSOCKET_T]);
424
+ return { kind: "libCall", fn: "net.createServerCb", args: [cb], type: NETSERVER_T, loc };
425
+ }
426
+ L.noLowering(`createServer with ${args.length} arguments`, expr, "the supported forms are createServer() and createServer(socket => ...)");
427
+ }
428
+ if (bi.member === "connect" || bi.member === "createConnection") {
429
+ // The options-object form — connect({ port, host?, autoSelectFamily?,
430
+ // lookup? }): portless's createLoopbackConnection. A `lookup` function
431
+ // OVERRIDES resolution: the runtime invokes it as Node does —
432
+ // lookup(hostname, options, callback) with callback(err, addresses)
433
+ // — and dials the answered addresses IN ORDER, each connect failure
434
+ // trying the next (the fixed-list autoSelectFamily behavior;
435
+ // autoSelectFamily must be the literal `true` alongside it, portless's
436
+ // spelling — without it Node would dial only the first address). The
437
+ // no-lookup form is connect(port, host) with the option spellings.
438
+ if (args.length >= 1 && ts.isObjectLiteralExpression(args[0])) {
439
+ return lowerNetConnectOptions(L, expr, bi.member, loc);
440
+ }
441
+ if (args.length < 1 || args.length > 3) {
442
+ L.noLowering(`${bi.member} with ${args.length} arguments`, expr, `the supported form is ${bi.member}(port, host?, connectListener?)`);
443
+ }
444
+ const port = L.lowerExprExpecting(args[0], F64);
445
+ // The optional middle host: a 2-arg call's second argument is the
446
+ // host when string-typed, the connect listener when func-typed.
447
+ let hostNode;
448
+ let cbNode;
449
+ if (args.length === 2) {
450
+ if (L.mapTypeOf(L.typeOf(args[1]))?.kind === "string")
451
+ hostNode = args[1];
452
+ else
453
+ cbNode = args[1];
454
+ }
455
+ else if (args.length === 3) {
456
+ hostNode = args[1];
457
+ cbNode = args[2];
458
+ }
459
+ const host = hostNode
460
+ ? L.lowerExprExpecting(hostNode, STRING)
461
+ : { kind: "strLit", value: "localhost", type: STRING, loc };
462
+ if (!cbNode) {
463
+ return { kind: "libCall", fn: "net.connect", args: [port, host], type: NETSOCKET_T, loc };
464
+ }
465
+ const { cb } = lowerCallbackArg(L, cbNode, "connect listeners", 0, () => false, "use ()", []);
466
+ return { kind: "libCall", fn: "net.connectCb", args: [port, host, cb], type: NETSOCKET_T, loc };
467
+ }
468
+ L.noLowering(`net.${bi.member}`, expr, "createServer, connect, and createConnection are the lowered net module functions", L.resolveValueSymbol(expr.expression));
469
+ }
470
+ /** True when `t` is the lookup-function shape the connect lowering
471
+ * accepts: (hostname: string, options: unknown, callback) => void where
472
+ * callback is (err: <union with a null arm>, addresses: { address:
473
+ * string, ... }[]) => void — Node's net.LookupFunction as portless
474
+ * declares it. */
475
+ function lookupFnShapeOk(L, t) {
476
+ if (t.kind !== "func" || t.ret.kind !== "void" || t.params.length !== 3)
477
+ return false;
478
+ if (t.params[0].kind !== "string")
479
+ return false;
480
+ if (t.params[1].kind !== "dyn")
481
+ return false;
482
+ const cbT = t.params[2];
483
+ if (cbT.kind !== "func" || cbT.ret.kind !== "void" || cbT.params.length !== 2)
484
+ return false;
485
+ const errT = cbT.params[0];
486
+ if (errT.kind !== "union")
487
+ return false;
488
+ const errDef = L.unions.get(errT.unionId);
489
+ if (!errDef || !errDef.arms.some((a) => a.kind === "nullT"))
490
+ return false;
491
+ // Every non-null arm must be the Error root: the emitted answer thunk
492
+ // reads .message off a non-null payload.
493
+ if (!errDef.arms.every((a) => a.kind === "nullT" || (a.kind === "object" && a.className === "%Error"))) {
494
+ return false;
495
+ }
496
+ const addrsT = cbT.params[1];
497
+ if (addrsT.kind !== "array" || addrsT.elem.kind !== "record")
498
+ return false;
499
+ const shape = L.shapes.get(addrsT.elem.shapeId);
500
+ return !!shape && shape.fields.some((f) => f.name === "address" && f.type.kind === "string");
501
+ }
502
+ /** net.connect / net.createConnection with an OPTIONS OBJECT: { port,
503
+ * host?, autoSelectFamily?, lookup? }. Without `lookup` this is the
504
+ * (port, host) form in option spelling; with it the runtime drives the
505
+ * caller's resolver and dials its answered addresses in order (the
506
+ * fixed-list autoSelectFamily behavior — the literal `true` is required
507
+ * alongside, portless's createLoopbackConnection). Other keys fence by
508
+ * name; a connect listener argument is supported on the no-lookup form
509
+ * only. */
510
+ function lowerNetConnectOptions(L, expr, member, loc) {
511
+ const args = expr.arguments;
512
+ const optsNode = args[0];
513
+ let port = null;
514
+ let host = null;
515
+ let lookup = null;
516
+ let autoSelect = false;
517
+ for (const prop of optsNode.properties) {
518
+ let initializer;
519
+ if (ts.isPropertyAssignment(prop) &&
520
+ (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) {
521
+ initializer = prop.initializer;
522
+ }
523
+ else if (ts.isShorthandPropertyAssignment(prop)) {
524
+ initializer = null;
525
+ }
526
+ else {
527
+ L.noLowering(`${member} options with computed keys or spreads`, prop, "each option must be a plain `name: value` (or shorthand) entry with a literal key");
528
+ }
529
+ const key = prop.name.text;
530
+ const lowerVal = () => initializer !== null
531
+ ? L.lowerExpr(initializer)
532
+ : L.lowerShorthandValue(prop);
533
+ if (key === "port") {
534
+ port = lowerVal();
535
+ if (port.type.kind !== "f64") {
536
+ L.noLowering(`a ${member} 'port' option of '${L.fmt(port.type)}' values`, prop, "the port is a number here");
537
+ }
538
+ }
539
+ else if (key === "host") {
540
+ host = lowerVal();
541
+ if (host.type.kind !== "string") {
542
+ L.noLowering(`a ${member} 'host' option of '${L.fmt(host.type)}' values`, prop, "the host is a string here");
543
+ }
544
+ }
545
+ else if (key === "autoSelectFamily") {
546
+ // The literal `true` only: it is what licenses the try-each-address
547
+ // dial below (false/dynamic would mean Node's single-address dial,
548
+ // which the lookup form does not implement).
549
+ if (initializer === null || initializer.kind !== ts.SyntaxKind.TrueKeyword) {
550
+ L.noLowering(`${member} with a non-literal autoSelectFamily option`, prop, "the lowered form is autoSelectFamily: true beside a lookup function");
551
+ }
552
+ autoSelect = true;
553
+ }
554
+ else if (key === "lookup") {
555
+ if (initializer === null) {
556
+ L.noLowering(`${member} with a shorthand lookup option`, prop, "spell it out: lookup: theResolver");
557
+ }
558
+ lookup = L.lowerExpr(initializer);
559
+ if (!lookupFnShapeOk(L, lookup.type)) {
560
+ L.noLowering(`a ${member} 'lookup' option of '${L.fmt(lookup.type)}' values`, prop, "the lowered resolver is (hostname: string, options: unknown, callback: (err: NodeJS.ErrnoException | null, addresses: { address: string; family: number }[]) => void) => void");
561
+ }
562
+ }
563
+ else {
564
+ L.noLowering(`${member} option '${key}'`, prop, "port, host, autoSelectFamily, and lookup are the supported options");
565
+ }
566
+ }
567
+ if (port === null) {
568
+ L.noLowering(`${member} options without a port`, optsNode, "the supported options object is { port, host?, autoSelectFamily?, lookup? }");
569
+ }
570
+ host ??= { kind: "strLit", value: "localhost", type: STRING, loc };
571
+ if (lookup !== null) {
572
+ if (!autoSelect) {
573
+ L.noLowering(`${member} with a lookup but no autoSelectFamily: true`, optsNode, "the lookup form dials every answered address in order — Node does that under autoSelectFamily: true, so spell it");
574
+ }
575
+ if (args.length !== 1) {
576
+ L.noLowering(`${member} with a lookup and a connect listener`, expr, "register the listener separately: socket.once('connect', ...)");
577
+ }
578
+ return { kind: "libCall", fn: "net.connectLookup", args: [port, host, lookup], type: NETSOCKET_T, loc };
579
+ }
580
+ if (args.length === 1) {
581
+ return { kind: "libCall", fn: "net.connect", args: [port, host], type: NETSOCKET_T, loc };
582
+ }
583
+ if (args.length !== 2) {
584
+ L.noLowering(`${member} with ${args.length} arguments`, expr, `the supported form is ${member}(options[, connectListener])`);
585
+ }
586
+ const { cb } = lowerCallbackArg(L, args[1], "connect listeners", 0, () => false, "use ()", []);
587
+ return { kind: "libCall", fn: "net.connectCb", args: [port, host, cb], type: NETSOCKET_T, loc };
588
+ }
589
+ /** `wrapper.close.bind(wrapper)` — the bound REAL close as a VALUE (the
590
+ * portless close-proxy idiom, paired with the `wrapper.close = fn`
591
+ * override assignment): a compiler-emitted closure over the server whose
592
+ * invocation closes DIRECTLY (never through the override, so the
593
+ * override body's `origClose(cb)` cannot recurse) and answers the server
594
+ * (Node's chaining return). The bind argument must be the same server
595
+ * binding as the method receiver — any other `this` would be a
596
+ * different close. Null when the shape doesn't match (generic fences
597
+ * name `.bind` otherwise). */
598
+ function lowerServerCloseBind(L, call, access) {
599
+ if (call.questionDotToken || access.questionDotToken)
600
+ return null;
601
+ if (access.name.text !== "bind")
602
+ return null;
603
+ const closeAccess = access.expression;
604
+ if (!ts.isPropertyAccessExpression(closeAccess) || closeAccess.name.text !== "close")
605
+ return null;
606
+ if (L.mapTypeOf(L.typeOf(closeAccess.expression))?.kind !== "netServer")
607
+ return null;
608
+ if (!L.isStdlibMember(closeAccess))
609
+ return null;
610
+ const loc = locOf(call);
611
+ const recvNode = closeAccess.expression;
612
+ const thisArg = call.arguments[0];
613
+ if (call.arguments.length !== 1 ||
614
+ !thisArg ||
615
+ !ts.isIdentifier(recvNode) ||
616
+ !ts.isIdentifier(thisArg) ||
617
+ L.resolveValueSymbol(recvNode) !== L.resolveValueSymbol(thisArg)) {
618
+ L.noLowering("close.bind with this argument shape", call, "the supported form is server.close.bind(server) — the same server binding on both sides");
619
+ }
620
+ const server = L.lowerExpr(thisArg);
621
+ const t = L.mapTypeOf(L.typeOf(call));
622
+ const cbUnion = t?.kind === "func" && t.params.length === 1 ? t.params[0] : null;
623
+ const cbOk = cbUnion?.kind === "union" &&
624
+ (() => {
625
+ const def = L.unions.get(cbUnion.unionId);
626
+ return (!!def &&
627
+ def.arms.some((a) => a.kind === "func" && a.params.length <= 1 && a.ret.kind === "void") &&
628
+ def.arms.some((a) => a.kind === "undefinedT"));
629
+ })();
630
+ // Both type worlds pass: @types/node spells (callback?: (err?: Error)
631
+ // => void) => Server; the shipped fallback (callback?: () => void) =>
632
+ // void — the emitted adapter follows the mapped shape either way.
633
+ if (!t || t.kind !== "func" || !cbOk || (t.ret.kind !== "netServer" && t.ret.kind !== "void")) {
634
+ L.noLowering("close.bind with this signature", call, "the bound close is (callback?: (err?: Error) => void) => Server");
635
+ }
636
+ return { kind: "libCall", fn: "net.serverCloseBind", args: [server], type: t, loc };
637
+ }
638
+ /** `wrapper.close = fn` (the close-override assignment — lower-stmts
639
+ * routes property-assignment statements here before its generic fence):
640
+ * the function value MOVES into the server's override slot behind an
641
+ * emitted zero-arg wrapper; server.close() runs it instead of closing,
642
+ * and the override reaches the real close through its bound origClose.
643
+ * Null when the target isn't a net.Server close member. */
644
+ export function lowerServerCloseOverrideAssignment(L, left, right, loc) {
645
+ if (!ts.isPropertyAccessExpression(left) || left.questionDotToken)
646
+ return null;
647
+ if (left.name.text !== "close")
648
+ return null;
649
+ if (L.mapTypeOf(L.typeOf(left.expression))?.kind !== "netServer")
650
+ return null;
651
+ const server = L.lowerExpr(left.expression);
652
+ const value = L.lowerExpr(right);
653
+ const t = value.type;
654
+ const cbUnion = t.kind === "func" && t.params.length === 1 ? t.params[0] : null;
655
+ const shapeOk = cbUnion?.kind === "union" &&
656
+ (t.kind === "func" && (t.ret.kind === "netServer" || t.ret.kind === "void")) &&
657
+ (() => {
658
+ const def = L.unions.get(cbUnion.unionId);
659
+ return (!!def &&
660
+ def.arms.some((a) => a.kind === "func" && a.params.length <= 1 && a.ret.kind === "void") &&
661
+ def.arms.some((a) => a.kind === "undefinedT"));
662
+ })();
663
+ if (!shapeOk) {
664
+ L.noLowering("assigning this value to server.close", right, "the override must be a (callback?: (err?: Error) => void) => Server function");
665
+ }
666
+ return {
667
+ kind: "exprStmt",
668
+ expr: { kind: "libCall", fn: "net.serverSetCloseOverride", args: [server, value], type: VOID, loc },
669
+ loc,
670
+ };
671
+ }
672
+ /** `res.statusCode = 404` / `res.statusMessage = "Nope"` — Node's
673
+ * writable ServerResponse properties (the implicit head reads them):
674
+ * routed from lower-stmts' property-assignment path beside the
675
+ * close-override hook. Null when the target isn't one of the two. */
676
+ export function lowerHttpResPropertyAssignment(L, left, right, loc) {
677
+ if (!ts.isPropertyAccessExpression(left) || left.questionDotToken)
678
+ return null;
679
+ const name = left.name.text;
680
+ if (name !== "statusCode" && name !== "statusMessage")
681
+ return null;
682
+ if (L.mapTypeOf(L.typeOf(left.expression))?.kind !== "httpRes")
683
+ return null;
684
+ if (!L.isStdlibMember(left))
685
+ return null;
686
+ const receiver = L.lowerExpr(left.expression);
687
+ const value = name === "statusCode"
688
+ ? L.lowerExprExpecting(right, F64)
689
+ : L.lowerExprExpecting(right, STRING);
690
+ const fn = name === "statusCode" ? "http.resStatusSet" : "http.resStatusMsgSet";
691
+ return {
692
+ kind: "exprStmt",
693
+ expr: { kind: "libCall", fn, args: [receiver, value], type: VOID, loc },
694
+ loc,
695
+ };
696
+ }
697
+ /** Method calls on net.Server receivers: listen(port[, cb]), close([cb]),
698
+ * on/once("connection" | "error" | "close", cb). address() alone fences
699
+ * toward the composed address().port read. Null for other receivers. */
700
+ function lowerNetServerMethodCall(L, call, access) {
701
+ if (L.mapTypeOf(L.typeOf(access.expression))?.kind !== "netServer")
702
+ return null;
703
+ if (!L.isStdlibMember(access))
704
+ return null;
705
+ const name = access.name.text;
706
+ const loc = locOf(call);
707
+ const args = call.arguments;
708
+ if (name === "listen") {
709
+ if (args.length < 1 || args.length > 3) {
710
+ L.noLowering(`listen with ${args.length} arguments`, call, "the supported form is listen(port[, host][, callback]) — port 0 binds an ephemeral port");
711
+ }
712
+ // Node answers the server (`return this` chaining — `const s =
713
+ // http.createServer(...).listen(0)`, `server.listen(0).address()`):
714
+ // statement position keeps the plain void call; a consumed result
715
+ // rides the interned receiver-returning helper.
716
+ const listenResult = (fn, callArgs) => {
717
+ if (ts.isExpressionStatement(call.parent)) {
718
+ return { kind: "libCall", fn, args: callArgs, type: VOID, loc };
719
+ }
720
+ return receiverReturningCall(L, fn, callArgs, NETSERVER_T, loc);
721
+ };
722
+ const receiver = handleReceiver(L, access.expression, NETSERVER_T);
723
+ // The options-object form — listen({ port, host?, ipv6Only? }[, cb]),
724
+ // the portless listenOnProxyInterface shape: host binds that ONE
725
+ // address (an IP literal — the runtime has no resolver here; absent
726
+ // = Node's host-less dual-stack any), ipv6Only sets IPV6_V6ONLY
727
+ // (truthiness, like Node's option handling — `boolean | undefined`
728
+ // flows). Every other key fences by name.
729
+ if (ts.isObjectLiteralExpression(args[0])) {
730
+ let port = null;
731
+ let host = null;
732
+ let v6only = null;
733
+ for (const prop of args[0].properties) {
734
+ let initializer;
735
+ if (ts.isPropertyAssignment(prop) &&
736
+ (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) {
737
+ initializer = prop.initializer;
738
+ }
739
+ else if (ts.isShorthandPropertyAssignment(prop)) {
740
+ initializer = null;
741
+ }
742
+ else {
743
+ L.noLowering("listen options with computed keys or spreads", prop, "each option must be a plain `name: value` (or shorthand) entry with a literal key");
744
+ }
745
+ const key = prop.name.text;
746
+ if (key === "port") {
747
+ port = initializer !== null
748
+ ? L.lowerExpr(initializer)
749
+ : L.lowerShorthandValue(prop);
750
+ if (port.type.kind !== "f64") {
751
+ L.noLowering(`a listen 'port' option of '${L.fmt(port.type)}' values`, prop, "the port is a number here");
752
+ }
753
+ }
754
+ else if (key === "host") {
755
+ host = initializer !== null
756
+ ? L.lowerExpr(initializer)
757
+ : L.lowerShorthandValue(prop);
758
+ if (host.type.kind !== "string") {
759
+ L.noLowering(`a listen 'host' option of '${L.fmt(host.type)}' values`, prop, "the host is an IP string here");
760
+ }
761
+ }
762
+ else if (key === "ipv6Only") {
763
+ if (initializer !== null) {
764
+ v6only = L.lowerCondition(initializer); /* truthiness: `boolean | undefined` flows */
765
+ }
766
+ else {
767
+ const v = L.lowerShorthandValue(prop);
768
+ if (v.type.kind !== "bool") {
769
+ L.noLowering(`a listen 'ipv6Only' option of '${L.fmt(v.type)}' values`, prop, "spell the option out (ipv6Only: value) so non-boolean values can narrow");
770
+ }
771
+ v6only = v;
772
+ }
773
+ }
774
+ else {
775
+ L.noLowering(`listen option '${key}'`, prop, "port, host, and ipv6Only are the supported listen options");
776
+ }
777
+ }
778
+ if (port === null) {
779
+ L.noLowering("listen options without a port", args[0], "the supported options object is { port, host?, ipv6Only? } — port 0 binds an ephemeral port");
780
+ }
781
+ host ??= { kind: "strLit", value: "", type: STRING, loc }; /* "" = the dual-stack any default */
782
+ v6only ??= boolLit(false, loc);
783
+ if (args.length === 1) {
784
+ return listenResult("net.listenOpts", [receiver, port, host, v6only]);
785
+ }
786
+ // The callback may be an OPTIONAL binding — `(() => void) |
787
+ // undefined`, portless's listenOnProxyInterface pass-through: the
788
+ // emitter unwraps the union (the SNICallback conditional-spread
789
+ // pattern) and the runtime takes NULL for the undefined arm. A
790
+ // checked-dynamic callback (a JS wrapper value) adapts through the
791
+ // dynCheck function boundary.
792
+ let cbV = L.lowerExpr(args[1]);
793
+ if (cbV.type.kind === "dyn") {
794
+ cbV = { kind: "dynCheck", value: cbV, type: funcOf([], VOID), loc };
795
+ }
796
+ const cbFuncOk = (t) => t.kind === "func" && t.params.length === 0 && t.ret.kind === "void";
797
+ const cbOk = cbFuncOk(cbV.type) ||
798
+ (cbV.type.kind === "union" &&
799
+ (() => {
800
+ const def = L.unions.get(cbV.type.unionId);
801
+ return (!!def &&
802
+ def.arms.length === 2 &&
803
+ def.arms.some(cbFuncOk) &&
804
+ def.arms.some((a) => a.kind === "undefinedT"));
805
+ })());
806
+ if (!cbOk) {
807
+ L.unsupported("SC1090", args[1], `listen callbacks of type '${L.fmt(cbV.type)}' (use () — an optional \`(() => void) | undefined\` binding also flows)`);
808
+ }
809
+ return listenResult("net.listenOptsCb", [receiver, port, host, v6only, cbV]);
810
+ }
811
+ const port = L.lowerExprExpecting(args[0], F64);
812
+ // The optional middle host — listen(port, '127.0.0.1'[, cb]): a
813
+ // string-typed second argument is the bind address (the net.connect
814
+ // disambiguation), routed through the options-form entry.
815
+ let hostNode;
816
+ let cbNode;
817
+ if (args.length === 2) {
818
+ if (L.mapTypeOf(L.typeOf(args[1]))?.kind === "string")
819
+ hostNode = args[1];
820
+ else
821
+ cbNode = args[1];
822
+ }
823
+ else if (args.length === 3) {
824
+ hostNode = args[1];
825
+ cbNode = args[2];
826
+ }
827
+ if (hostNode !== undefined) {
828
+ const host = L.lowerExprExpecting(hostNode, STRING);
829
+ const v6only = boolLit(false, loc);
830
+ if (!cbNode)
831
+ return listenResult("net.listenOpts", [receiver, port, host, v6only]);
832
+ const { cb } = lowerCallbackArg(L, cbNode, "listen callbacks", 0, () => false, "use ()", []);
833
+ return listenResult("net.listenOptsCb", [receiver, port, host, v6only, cb]);
834
+ }
835
+ if (!cbNode) {
836
+ return listenResult("net.listen", [receiver, port]);
837
+ }
838
+ const { cb } = lowerCallbackArg(L, cbNode, "listen callbacks", 0, () => false, "use ()", []);
839
+ return listenResult("net.listenCb", [receiver, port, cb]);
840
+ }
841
+ if (name === "close") {
842
+ requireStatementPosition(L, call, "server.close(...)");
843
+ if (args.length > 1) {
844
+ L.noLowering(`close with ${args.length} arguments`, call, "the supported form is close([callback])");
845
+ }
846
+ const receiver = handleReceiver(L, access.expression, NETSERVER_T);
847
+ if (args.length === 0) {
848
+ return { kind: "libCall", fn: "net.serverClose", args: [receiver], type: VOID, loc };
849
+ }
850
+ const { cb } = lowerCallbackArg(L, args[0], "close callbacks", 0, () => false, "use ()", []);
851
+ return { kind: "libCall", fn: "net.serverCloseCb", args: [receiver, cb], type: VOID, loc };
852
+ }
853
+ if ((name === "on" || name === "once" || name === "addListener") && args.length === 2) {
854
+ requireStatementPosition(L, call, `server.${name}(...)`);
855
+ const once = boolLit(name === "once", loc);
856
+ const evT = L.typeOf(args[0]);
857
+ const event = evT.isStringLiteralType() ? evT.value : null;
858
+ const receiver = handleReceiver(L, access.expression, NETSERVER_T);
859
+ if (event === "connection" || event === "secureConnection") {
860
+ // 'secureConnection' rides the same list: a TLS server's
861
+ // 'connection' listeners are DEFERRED to handshake completion
862
+ // (scr_net_server_defer_connections), which IS Node's
863
+ // secureConnection timing; on a plain net server the name would
864
+ // never fire in Node either — the runtime entry gates on the
865
+ // deferral flag so the split stays exact.
866
+ const { cb } = lowerCallbackArg(L, args[1], `${event} listeners`, 1, (p) => p.kind === "netSocket", "use (socket) or ()", [NETSOCKET_T]);
867
+ const fn = event === "connection" ? "net.serverOnConnection" : "net.serverOnSecureConnection";
868
+ return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc };
869
+ }
870
+ if (event === "error") {
871
+ const { cb } = lowerCallbackArg(L, args[1], "error listeners", 1, (p) => p.kind === "object" && p.className === "%Error", "use (err) or ()", [ERROR_T]);
872
+ return { kind: "libCall", fn: "net.serverOnError", args: [receiver, cb, once], type: VOID, loc };
873
+ }
874
+ if (event === "close") {
875
+ const { cb } = lowerCallbackArg(L, args[1], "close listeners", 0, () => false, "use ()", []);
876
+ return { kind: "libCall", fn: "net.serverOnClose", args: [receiver, cb, once], type: VOID, loc };
877
+ }
878
+ if (event === "listening") {
879
+ // The deferred bind emit — listen(port, cb)'s event twin: fires
880
+ // once after a successful listen; late registrations on an
881
+ // already-listening server never fire (Node's once-per-listen).
882
+ const { cb } = lowerCallbackArg(L, args[1], "listening listeners", 0, () => false, "use ()", []);
883
+ return { kind: "libCall", fn: "net.serverOnListening", args: [receiver, cb, once], type: VOID, loc };
884
+ }
885
+ if (event === "request") {
886
+ // The 'request' event — http.createServer(handler)'s event twin, and
887
+ // the way an http2 allowHTTP1 server (created handler-less) gets its
888
+ // handler. Same (req, res) shapes as the createServer callback; on a
889
+ // server with no HTTP parser (net.createServer) the registration is
890
+ // Node-honest dead weight: the event never fires there either.
891
+ const cb = lowerRequestHandlerArg(L, args[1]);
892
+ return { kind: "libCall", fn: "http.serverOnRequest", args: [receiver, cb, once], type: VOID, loc };
893
+ }
894
+ if (event === "stream") {
895
+ // The h2c server's request event: (stream, headers, flags). The
896
+ // adapter builds the headers record from the pairs the runtime
897
+ // hands over. Dead weight on a non-h2 server (never fires).
898
+ const cb = h2HeadersCallbackAdapter(L, args[1], "stream listeners", "http2Stream", false, loc);
899
+ return { kind: "libCall", fn: "http2.serverOnStream", args: [receiver, cb, once], type: VOID, loc };
900
+ }
901
+ if (event === "session") {
902
+ // The h2c server's per-connection session event: (session). The
903
+ // result is voidized; the handle passes by reference.
904
+ const { cb } = lowerCallbackArg(L, args[1], "session listeners", 1, (p) => p.kind === "http2Session", "use (session) or ()", [HTTP2SESSION_T]);
905
+ return { kind: "libCall", fn: "http2.serverOnSession", args: [receiver, cb, once], type: VOID, loc };
906
+ }
907
+ if (event === "connect") {
908
+ // HTTP CONNECT — the tunneling handover: (req, socket, head), fired
909
+ // INSTEAD of 'request' for CONNECT-method requests (the 'upgrade'
910
+ // machinery's twin; no listener destroys the socket, Node's
911
+ // default). Under the allowHTTP1 lowering the h2 compat server's
912
+ // 'connect' only ever fires for HTTP/1.1 CONNECT, so the second
913
+ // argument is ALWAYS the raw socket — a listener typing it
914
+ // `Http2ServerResponse | net.Socket` (portless's RFC 8441 handler)
915
+ // takes the union with the socket wrapped at its arm; `instanceof
916
+ // net.Socket` narrows it (always true at runtime here, exactly like
917
+ // Node's allowHTTP1 HTTP/1.1 arm).
918
+ const cb = L.lowerExpr(args[1]);
919
+ if (cb.type.kind !== "func" || cb.type.params.length > 3) {
920
+ L.unsupported("SC1090", args[1], "connect listeners with more than three parameters (use (req, socket, head))");
921
+ }
922
+ if (cb.type.ret.kind !== "void") {
923
+ L.unsupported("SC1090", args[1], "connect listeners returning a value (make the callback body a block)");
924
+ }
925
+ const [p0, p1, p2] = cb.type.params;
926
+ const sockParamOk = (t) => {
927
+ if (t.kind === "netSocket")
928
+ return true;
929
+ if (t.kind !== "union")
930
+ return false;
931
+ const def = L.unions.get(t.unionId);
932
+ return !!def && def.arms.some((a) => a.kind === "netSocket");
933
+ };
934
+ if ((p0 !== undefined && p0.kind !== "httpReq") ||
935
+ (p1 !== undefined && !sockParamOk(p1)) ||
936
+ (p2 !== undefined && !(p2.kind === "bytes" && p2.elem === "u8"))) {
937
+ L.unsupported("SC1090", args[1], "connect listeners whose parameters are not (req: IncomingMessage, socket: Socket — or a union carrying the Socket arm, the h2 compat shape, head: Buffer)");
938
+ }
939
+ return { kind: "libCall", fn: "http.serverOnConnect", args: [receiver, cb, once], type: VOID, loc };
940
+ }
941
+ if (event === "upgrade") {
942
+ // The WebSocket handover: (req, socket, head) — Node fires it
943
+ // INSTEAD of 'request' for Connection: upgrade requests, hands the
944
+ // socket over raw with the bytes read past the head, and destroys
945
+ // the socket when no listener exists. Registration works on every
946
+ // server kind (a parserless net server never fires it, like Node).
947
+ const cb = L.lowerExpr(args[1]);
948
+ if (cb.type.kind !== "func" || cb.type.params.length > 3) {
949
+ L.unsupported("SC1090", args[1], "upgrade listeners with more than three parameters (use (req, socket, head))");
950
+ }
951
+ if (cb.type.ret.kind !== "void") {
952
+ L.unsupported("SC1090", args[1], "upgrade listeners returning a value (make the callback body a block)");
953
+ }
954
+ const [p0, p1, p2] = cb.type.params;
955
+ if ((p0 !== undefined && p0.kind !== "httpReq") ||
956
+ (p1 !== undefined && p1.kind !== "netSocket") ||
957
+ (p2 !== undefined && !(p2.kind === "bytes" && p2.elem === "u8"))) {
958
+ L.unsupported("SC1090", args[1], "upgrade listeners whose parameters are not (req: IncomingMessage, socket: Socket, head: Buffer)");
959
+ }
960
+ return { kind: "libCall", fn: "http.serverOnUpgrade", args: [receiver, cb, once], type: VOID, loc };
961
+ }
962
+ if (event === "sessionError") {
963
+ // 'sessionError' is an HTTP/2 SESSION event — the allowHTTP1
964
+ // lowering serves every connection as HTTP/1.1, so no session ever
965
+ // exists and the event NEVER fires (SEMANTICS.md divergence 57).
966
+ // The registration is honest dead weight: the callback is built and
967
+ // released, never called — so its parameters need no checking
968
+ // beyond the closure shape.
969
+ const cb = L.lowerExpr(args[1]);
970
+ if (cb.type.kind !== "func" || cb.type.ret.kind !== "void") {
971
+ L.unsupported("SC1090", args[1], "sessionError listeners returning a value (make the callback body a block)");
972
+ }
973
+ return { kind: "libCall", fn: "http2.serverOnSessionError", args: [receiver, cb], type: VOID, loc };
974
+ }
975
+ L.noLowering(`server.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, args[0], '"connection", "request", "stream", "session", "upgrade", "connect", "error", "close", "listening", and "sessionError" are the supported server events (as literals)');
976
+ }
977
+ if (name === "emit") {
978
+ // The demux route: emit("connection", socket) hands an accepted
979
+ // socket to ANOTHER server (portless's first-byte TLS peek). Only
980
+ // the literal "connection" form lowers; Node's boolean result is
981
+ // dropped (statement position required).
982
+ requireStatementPosition(L, call, "server.emit(...)");
983
+ const evT = args.length >= 1 ? L.typeOf(args[0]) : null;
984
+ const event = evT?.isStringLiteralType() ? evT.value : null;
985
+ if (event !== "connection" || args.length !== 2) {
986
+ L.noLowering(`server.emit(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call, 'emit("connection", socket) is the supported emit form (the demux route)');
987
+ }
988
+ const receiver = handleReceiver(L, access.expression, NETSERVER_T);
989
+ const sock = L.lowerExpr(args[1]);
990
+ if (sock.type.kind !== "netSocket") {
991
+ L.noLowering(`emit("connection", …) with a '${L.fmt(sock.type)}' argument`, args[1], "the second argument must be a net socket");
992
+ }
993
+ return { kind: "libCall", fn: "net.serverEmitConnection", args: [receiver, sock], type: VOID, loc };
994
+ }
995
+ if (name === "address") {
996
+ // The full AddressInfo record (the dgram.address materialization).
997
+ // The composed `server.address().port` read already lowered through
998
+ // the property path; this is the record-valued remainder (`const a =
999
+ // server.address()` — the listen-callback shape).
1000
+ if (args.length !== 0) {
1001
+ L.noLowering(`address with ${args.length} arguments`, call, "address() takes no arguments");
1002
+ }
1003
+ const result = L.mapTypeOf(L.typeOf(call));
1004
+ if (!result || !isAddressInfoRecord(L, result)) {
1005
+ L.noLowering("address() where the result is not the {address, family, port} record", call, "the AddressInfo shape is the supported result");
1006
+ }
1007
+ const receiver = handleReceiver(L, access.expression, NETSERVER_T);
1008
+ return { kind: "libCall", fn: "net.serverAddress", args: [receiver], type: result, loc };
1009
+ }
1010
+ L.noLowering(`Server.${name}`, call, "listen, close, address().port, emit(\"connection\", socket), and on/once of connection/request/upgrade/error/close/listening/sessionError are the supported Server members", L.checker.getSymbolAtLocation(access.name));
1011
+ }
1012
+ /** Method calls on net.Socket receivers: write/end/destroy/pipe and
1013
+ * on/once("data" | "end" | "close" | "error" | "connect"). Null for
1014
+ * other receivers. */
1015
+ function lowerNetSocketMethodCall(L, call, access) {
1016
+ if (L.mapTypeOf(L.typeOf(access.expression))?.kind !== "netSocket")
1017
+ return null;
1018
+ if (!L.isStdlibMember(access))
1019
+ return null;
1020
+ const name = access.name.text;
1021
+ const loc = locOf(call);
1022
+ const args = call.arguments;
1023
+ if (name === "write" || name === "end") {
1024
+ requireStatementPosition(L, call, `socket.${name}(...)`);
1025
+ const maxArgs = name === "write" ? 1 : 1;
1026
+ const minArgs = name === "write" ? 1 : 0;
1027
+ if (args.length < minArgs || args.length > maxArgs) {
1028
+ L.noLowering(`${name} with ${args.length} arguments`, call, name === "write"
1029
+ ? "the supported form is write(data) — one string or Buffer"
1030
+ : "the supported forms are end() and end(data)");
1031
+ }
1032
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1033
+ if (args.length === 0) {
1034
+ return { kind: "libCall", fn: "net.sockEnd", args: [receiver], type: VOID, loc };
1035
+ }
1036
+ const data = L.lowerExpr(args[0]);
1037
+ if (data.type.kind === "string") {
1038
+ const fn = name === "write" ? "net.sockWrite" : "net.sockEndStr";
1039
+ return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc };
1040
+ }
1041
+ if (data.type.kind === "bytes" && data.type.elem === "u8") {
1042
+ const fn = name === "write" ? "net.sockWriteBytes" : "net.sockEndBytes";
1043
+ return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc };
1044
+ }
1045
+ if (data.type.kind === "dyn") {
1046
+ // An untyped JS payload (a helper's parameter): the runtime
1047
+ // dispatches STR/BYTES and throws Node's chunk TypeError otherwise.
1048
+ const fn = name === "write" ? "net.sockWriteDyn" : "net.sockEndDyn";
1049
+ return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc };
1050
+ }
1051
+ L.noLowering(`${name} of '${L.fmt(data.type)}' data`, args[0] ?? call, NARROW_DATA_HINT);
1052
+ }
1053
+ if (name === "destroy") {
1054
+ requireStatementPosition(L, call, "socket.destroy()");
1055
+ if (args.length !== 0) {
1056
+ L.noLowering(`destroy with ${args.length} arguments`, call, "destroy() takes no arguments here");
1057
+ }
1058
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1059
+ return { kind: "libCall", fn: "net.sockDestroy", args: [receiver], type: VOID, loc };
1060
+ }
1061
+ if (name === "setEncoding") {
1062
+ // The req twin: utf8 flips 'data' delivery to strings.
1063
+ requireStatementPosition(L, call, "socket.setEncoding(...)");
1064
+ if (args.length !== 1) {
1065
+ L.noLowering(`setEncoding with ${args.length} arguments`, call, "the supported form is setEncoding(encoding)");
1066
+ }
1067
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1068
+ const enc = L.lowerExprExpecting(args[0], STRING);
1069
+ return { kind: "libCall", fn: "net.sockSetEncoding", args: [receiver, enc], type: VOID, loc };
1070
+ }
1071
+ if (name === "setTimeout") {
1072
+ requireStatementPosition(L, call, "socket.setTimeout(...)");
1073
+ if (args.length !== 1) {
1074
+ L.noLowering(`setTimeout with ${args.length} arguments`, call, "the supported form is setTimeout(ms) — register the callback separately: socket.once('timeout', ...)");
1075
+ }
1076
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1077
+ const ms = L.lowerExprExpecting(args[0], F64);
1078
+ return { kind: "libCall", fn: "net.sockSetTimeout", args: [receiver, ms], type: VOID, loc };
1079
+ }
1080
+ if (name === "read") {
1081
+ // socket.read(n?) — the demux peek. Answers the interned
1082
+ // `Buffer | null` union: exactly n buffered bytes, or null (Node's
1083
+ // less-than-n answer); read() drains the whole buffer.
1084
+ if (args.length > 1) {
1085
+ L.noLowering(`read with ${args.length} arguments`, call, "the supported forms are read() and read(n)");
1086
+ }
1087
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1088
+ const n = args.length === 1
1089
+ ? L.lowerExprExpecting(args[0], F64)
1090
+ : { kind: "numLit", value: 0, type: F64, loc };
1091
+ const type = { kind: "union", unionId: L.unions.intern([BYTES_U8, NULL_T]) };
1092
+ return { kind: "libCall", fn: "net.sockRead", args: [receiver, n], type, loc };
1093
+ }
1094
+ if (name === "unshift") {
1095
+ requireStatementPosition(L, call, "socket.unshift(...)");
1096
+ if (args.length !== 1) {
1097
+ L.noLowering(`unshift with ${args.length} arguments`, call, "the supported form is unshift(buffer)");
1098
+ }
1099
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1100
+ const data = L.lowerExpr(args[0]);
1101
+ if (data.type.kind !== "bytes" || data.type.elem !== "u8") {
1102
+ L.noLowering(`unshift of '${L.fmt(data.type)}' data`, args[0], "unshift takes one Buffer/Uint8Array (narrow unions first — the read(1) result needs its null arm checked)");
1103
+ }
1104
+ return { kind: "libCall", fn: "net.sockUnshift", args: [receiver, data], type: VOID, loc };
1105
+ }
1106
+ if (name === "pipe") {
1107
+ requireStatementPosition(L, call, "socket.pipe(...)");
1108
+ if (args.length !== 1) {
1109
+ L.noLowering(`pipe with ${args.length} arguments`, call, "the supported form is pipe(destination)");
1110
+ }
1111
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1112
+ const dst = L.lowerExpr(args[0]);
1113
+ if (dst.type.kind === "httpRes") {
1114
+ // socket → ServerResponse (the extended-CONNECT bridge leg): raw
1115
+ // chunks become response body writes; EOF ends the response.
1116
+ return { kind: "libCall", fn: "net.sockPipeRes", args: [receiver, dst], type: VOID, loc };
1117
+ }
1118
+ if (dst.type.kind !== "netSocket") {
1119
+ L.noLowering(`pipe into '${L.fmt(dst.type)}' destinations`, args[0], "a socket pipes into another socket or a ServerResponse");
1120
+ }
1121
+ return { kind: "libCall", fn: "net.sockPipe", args: [receiver, dst], type: VOID, loc };
1122
+ }
1123
+ if ((name === "on" || name === "once" || name === "addListener") && args.length === 2) {
1124
+ requireStatementPosition(L, call, `socket.${name}(...)`);
1125
+ const once = boolLit(name === "once", loc);
1126
+ const evT = L.typeOf(args[0]);
1127
+ const event = evT.isStringLiteralType() ? evT.value : null;
1128
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1129
+ if (event === "data") {
1130
+ const { cb } = lowerCallbackArg(L, args[1], "data listeners", 1, (p) => p.kind === "bytes" && p.elem === "u8", "use (chunk: Buffer) or ()", [DYN]);
1131
+ return { kind: "libCall", fn: "net.sockOnData", args: [receiver, cb, once], type: VOID, loc };
1132
+ }
1133
+ if (event === "error") {
1134
+ const { cb } = lowerCallbackArg(L, args[1], "error listeners", 1, (p) => p.kind === "object" && p.className === "%Error", "use (err) or ()", [ERROR_T]);
1135
+ return { kind: "libCall", fn: "net.sockOnError", args: [receiver, cb, once], type: VOID, loc };
1136
+ }
1137
+ if (event === "end" || event === "close" || event === "connect" || event === "timeout" ||
1138
+ event === "readable") {
1139
+ const { cb } = lowerCallbackArg(L, args[1], `${event} listeners`, 0, () => false, "use ()", []);
1140
+ const fn = event === "end" ? "net.sockOnEnd"
1141
+ : event === "close" ? "net.sockOnClose"
1142
+ : event === "timeout" ? "net.sockOnTimeout"
1143
+ : event === "readable" ? "net.sockOnReadable"
1144
+ : "net.sockOnConnect";
1145
+ return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc };
1146
+ }
1147
+ if (event === "secureConnect") {
1148
+ // The TLS client's post-handshake event: on a TLS socket the
1149
+ // 'connect' list already fires at establishment (scr_net.c's
1150
+ // transport pump); the runtime gates on the transport so a plain
1151
+ // socket's registration never fires — Node's exact split.
1152
+ const { cb } = lowerCallbackArg(L, args[1], "secureConnect listeners", 0, () => false, "use ()", []);
1153
+ return { kind: "libCall", fn: "tls.sockOnSecureConnect", args: [receiver, cb, once], type: VOID, loc };
1154
+ }
1155
+ if (event === "session") {
1156
+ // The received-ticket event: fires once per TLS client socket with
1157
+ // the serialized session (a Buffer); plain sockets never fire it.
1158
+ const { cb } = lowerCallbackArg(L, args[1], "session listeners", 1, (p) => p.kind === "bytes" && p.elem === "u8", "use (session: Buffer) or ()", [DYN]);
1159
+ return { kind: "libCall", fn: "tls.sockOnSession", args: [receiver, cb, once], type: VOID, loc };
1160
+ }
1161
+ L.noLowering(`socket.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, args[0], '"data", "end", "close", "error", "connect", "timeout", "readable", "secureConnect", and "session" are the supported socket events (as literals)');
1162
+ }
1163
+ L.noLowering(`Socket.${name}`, call, "write, end, destroy, pipe, setTimeout, remoteAddress, read, unshift, and on/once of data/end/close/error/connect/timeout/readable are the supported Socket members", L.checker.getSymbolAtLocation(access.name));
1164
+ }
1165
+ /** The method-call dispatch for both server-surface receiver kinds — one
1166
+ * entry in lower-calls.ts's intrinsic chain (the lowerChildMethodCall
1167
+ * slot). Null when the receiver is neither. */
1168
+ export function lowerServerMethodCall(L, call, access) {
1169
+ if (call.questionDotToken || access.questionDotToken)
1170
+ return null;
1171
+ return (lowerServerCloseBind(L, call, access) ??
1172
+ lowerNetServerMethodCall(L, call, access) ??
1173
+ lowerNetSocketMethodCall(L, call, access) ??
1174
+ lowerH2SessionMethodCall(L, call, access) ??
1175
+ lowerH2StreamMethodCall(L, call, access) ??
1176
+ lowerHttpReqMethodCall(L, call, access) ??
1177
+ lowerHttpResMethodCall(L, call, access) ??
1178
+ lowerHttpClientMethodCall(L, call, access));
1179
+ }
1180
+ /** The `{ ...req.headers }` SNAPSHOT helper, interned per target shape —
1181
+ * the envSnapshotHelper pattern over http.reqHeaderPairs: a fresh
1182
+ * pure-index record whose overflow holds every header (lowercased names,
1183
+ * arrival order), each value wrapped at the slot union's string arm. Null
1184
+ * when the shape is not a pure index-signature record with a string arm
1185
+ * (the caller fences). */
1186
+ function headersSnapshotHelper(L, shapeId, loc) {
1187
+ const shape = L.shapes.get(shapeId);
1188
+ if (!shape || shape.tuple || shape.fields.length > 0 || !shape.indexValue)
1189
+ return null;
1190
+ const iv = shape.indexValue;
1191
+ if (iv.kind !== "union")
1192
+ return null;
1193
+ const strTag = L.armTag(iv.unionId, STRING);
1194
+ if (strTag < 0)
1195
+ return null;
1196
+ const key = `headers.snapshot:${shapeId}`;
1197
+ const existing = L.widthHelpers.get(key);
1198
+ if (existing)
1199
+ return existing;
1200
+ const name = `%headers.snapshot.${L.widthHelpers.size}`;
1201
+ L.widthHelpers.set(key, name);
1202
+ const recT = { kind: "record", shapeId };
1203
+ const pairsT = arrayOf(STRING);
1204
+ const ref = (localId, type) => ({ kind: "varRef", localId, type, loc });
1205
+ const num = (value) => ({ kind: "numLit", value, type: F64, loc });
1206
+ const pairAt = (offset) => ({
1207
+ kind: "arrayGet",
1208
+ arr: ref("ps.0", pairsT),
1209
+ index: offset === 0
1210
+ ? ref("i.0", F64)
1211
+ : { kind: "bin", op: "+", left: ref("i.0", F64), right: num(offset), type: F64, loc },
1212
+ type: STRING,
1213
+ loc,
1214
+ });
1215
+ const body = [
1216
+ {
1217
+ kind: "varDecl",
1218
+ localId: "ps.0",
1219
+ init: { kind: "libCall", fn: "http.reqHeaderPairs", args: [ref("r.0", HTTPREQ_T)], type: pairsT, loc },
1220
+ loc,
1221
+ },
1222
+ {
1223
+ kind: "varDecl",
1224
+ localId: "out.0",
1225
+ init: { kind: "recordLit", fields: [], type: recT, loc },
1226
+ loc,
1227
+ },
1228
+ {
1229
+ kind: "for",
1230
+ init: { kind: "varDecl", localId: "i.0", init: num(0), loc },
1231
+ cond: {
1232
+ kind: "bin",
1233
+ op: "<",
1234
+ left: ref("i.0", F64),
1235
+ right: { kind: "arrIntrinsic", method: "length", receiver: ref("ps.0", pairsT), args: [], type: F64, loc },
1236
+ type: BOOL,
1237
+ loc,
1238
+ },
1239
+ update: {
1240
+ kind: "assign",
1241
+ localId: "i.0",
1242
+ value: { kind: "bin", op: "+", left: ref("i.0", F64), right: num(2), type: F64, loc },
1243
+ loc,
1244
+ },
1245
+ body: [
1246
+ {
1247
+ kind: "recordKeySet",
1248
+ obj: ref("out.0", recT),
1249
+ shapeId,
1250
+ key: pairAt(0),
1251
+ value: { kind: "unionWrap", unionId: iv.unionId, tag: strTag, value: pairAt(1), type: iv, loc },
1252
+ loc,
1253
+ },
1254
+ ],
1255
+ loc,
1256
+ },
1257
+ { kind: "return", value: ref("out.0", recT), loc },
1258
+ ];
1259
+ L.liftedFns.push({
1260
+ name,
1261
+ params: [{ localId: "r.0", name: "r", type: HTTPREQ_T }],
1262
+ returnType: recT,
1263
+ locals: [
1264
+ { id: "r.0", name: "r", type: HTTPREQ_T, mutable: false },
1265
+ { id: "ps.0", name: "ps", type: pairsT, mutable: false },
1266
+ { id: "out.0", name: "out", type: recT, mutable: false },
1267
+ { id: "i.0", name: "i", type: F64, mutable: true },
1268
+ ],
1269
+ body,
1270
+ loc,
1271
+ });
1272
+ return name;
1273
+ }
1274
+ /** The `endStream` boolean of an h2 options object literal, as a literal
1275
+ * bool. Returns undefined when absent (or the arg is absent). Fences on
1276
+ * a non-literal endStream value or unknown keys that would matter. */
1277
+ function h2EndStreamOption(L, node) {
1278
+ if (node === undefined)
1279
+ return undefined;
1280
+ if (!ts.isObjectLiteralExpression(node)) {
1281
+ L.noLowering("h2 options argument", node, "pass the options as an object literal ({ endStream: true })");
1282
+ }
1283
+ let end;
1284
+ for (const prop of node.properties) {
1285
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) {
1286
+ L.noLowering("h2 options with spreads or computed keys", prop, "use plain `name: value` entries");
1287
+ }
1288
+ const key = prop.name.text;
1289
+ if (key === "endStream") {
1290
+ if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword)
1291
+ end = true;
1292
+ else if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword)
1293
+ end = false;
1294
+ else
1295
+ L.noLowering("a non-literal endStream option", prop.initializer, "spell it true or false");
1296
+ }
1297
+ // Other keys (waitForTrailers, exclusive, parent, weight) are accepted
1298
+ // and ignored — the honest-defaults stance (they tune framing this
1299
+ // core does not model; a use that DEPENDS on them fails downstream).
1300
+ }
1301
+ return end;
1302
+ }
1303
+ /** Method calls on ClientHttp2Session / ServerHttp2Session receivers.
1304
+ * Null for other receivers (the chain keeps trying). */
1305
+ function lowerH2SessionMethodCall(L, call, access) {
1306
+ if (L.mapTypeOf(L.typeOf(access.expression))?.kind !== "http2Session")
1307
+ return null;
1308
+ if (!L.isStdlibMember(access))
1309
+ return null;
1310
+ const name = access.name.text;
1311
+ const loc = locOf(call);
1312
+ const args = call.arguments;
1313
+ const receiver = () => handleReceiver(L, access.expression, HTTP2SESSION_T);
1314
+ if (name === "request") {
1315
+ const pairs = args.length >= 1 && !ts.isObjectLiteralExpression(args[0]) === false && args.length >= 1
1316
+ ? null : null;
1317
+ void pairs;
1318
+ // arg0: headers (optional), arg1: options (optional).
1319
+ const headersArg = args.length >= 1 ? lowerH2HeadersArg(L, args[0]) : { kind: "arrayLit", elems: [], type: arrayOf(STRING), loc };
1320
+ const end = h2EndStreamOption(L, args[1]);
1321
+ const endF64 = { kind: "numLit", value: end === undefined ? -1 : end ? 1 : 0, type: F64, loc };
1322
+ return { kind: "libCall", fn: "http2.sessionRequest", args: [receiver(), headersArg, endF64], type: HTTP2STREAM_T, loc };
1323
+ }
1324
+ if (name === "close") {
1325
+ requireStatementPosition(L, call, "session.close(...)");
1326
+ if (args.length === 0) {
1327
+ return { kind: "libCall", fn: "http2.sessionClose", args: [receiver()], type: VOID, loc };
1328
+ }
1329
+ const { cb } = lowerCallbackArg(L, args[0], "close callbacks", 0, () => false, "use ()", []);
1330
+ return { kind: "libCall", fn: "http2.sessionCloseCb", args: [receiver(), cb], type: VOID, loc };
1331
+ }
1332
+ if (name === "destroy") {
1333
+ requireStatementPosition(L, call, "session.destroy(...)");
1334
+ return { kind: "libCall", fn: "http2.sessionDestroy", args: [receiver()], type: VOID, loc };
1335
+ }
1336
+ if (name === "settings") {
1337
+ // session.settings(obj[, cb]): the settings record crosses as a DOM
1338
+ // value (the checked-dynamic boundary — object literals box); the
1339
+ // callback fires on the peer's ACK ((err, settings, duration) in the
1340
+ // dyn flavor, zero-arg in the typed one).
1341
+ requireStatementPosition(L, call, "session.settings(...)");
1342
+ if (args.length === 0) {
1343
+ return { kind: "libCall", fn: "http2.sessionSettings0", args: [receiver()], type: VOID, loc };
1344
+ }
1345
+ const v = L.lowerExpr(args[0]);
1346
+ const settingsArg = v.type.kind === "dyn" ? v : { kind: "dynFrom", value: v, type: DYN, loc };
1347
+ if (args.length >= 2) {
1348
+ const cbV = L.lowerExpr(args[1]);
1349
+ if (cbV.type.kind === "dyn") {
1350
+ // The dyn callback keeps Node's exact (err, settings, duration)
1351
+ // shape — the runtime fires it through the DOM.
1352
+ return { kind: "libCall", fn: "http2.sessionSettingsDynCb", args: [receiver(), settingsArg, cbV], type: VOID, loc };
1353
+ }
1354
+ const { cb } = lowerCallbackArg(L, args[1], "settings callbacks", 0, () => false, "use () — or a dynamic (mustCall-wrapped) callback for the (err, settings, duration) shape", []);
1355
+ return { kind: "libCall", fn: "http2.sessionSettingsCb0", args: [receiver(), settingsArg, cb], type: VOID, loc };
1356
+ }
1357
+ return { kind: "libCall", fn: "http2.sessionSettings", args: [receiver(), settingsArg], type: VOID, loc };
1358
+ }
1359
+ if ((name === "on" || name === "once" || name === "addListener") && args.length === 2) {
1360
+ requireStatementPosition(L, call, `session.${name}(...)`);
1361
+ const once = boolLit(name === "once", loc);
1362
+ const evT = L.typeOf(args[0]);
1363
+ const event = evT.isStringLiteralType() ? evT.value : null;
1364
+ if (event === "localSettings" || event === "remoteSettings") {
1365
+ // The settings payload crosses as a DOM value: dyn (mustCall)
1366
+ // listeners fire with the settings record; zero-arg typed
1367
+ // listeners register plainly.
1368
+ const local = boolLit(event === "localSettings", loc);
1369
+ const cbV = L.lowerExpr(args[1]);
1370
+ if (cbV.type.kind === "dyn") {
1371
+ return { kind: "libCall", fn: "http2.sessionOnSettingsDyn", args: [receiver(), cbV, once, local], type: VOID, loc };
1372
+ }
1373
+ const { cb } = lowerCallbackArg(L, args[1], `${event} listeners`, 0, () => false, "use () — or a dynamic (mustCall-wrapped) listener for the (settings) payload", []);
1374
+ return { kind: "libCall", fn: "http2.sessionOnSettings0", args: [receiver(), cb, once, local], type: VOID, loc };
1375
+ }
1376
+ if (event === "close") {
1377
+ const { cb } = lowerCallbackArg(L, args[1], "close listeners", 0, () => false, "use ()", []);
1378
+ return { kind: "libCall", fn: "http2.sessionOnClose", args: [receiver(), cb, once], type: VOID, loc };
1379
+ }
1380
+ if (event === "error") {
1381
+ const { cb } = lowerCallbackArg(L, args[1], "error listeners", 1, (p) => p.kind === "object" && p.className === "%Error", "use (err) or ()", [ERROR_T]);
1382
+ return { kind: "libCall", fn: "http2.sessionOnError", args: [receiver(), cb, once], type: VOID, loc };
1383
+ }
1384
+ if (event === "connect") {
1385
+ const { cb } = lowerCallbackArg(L, args[1], "connect listeners", 2, (p) => p.kind === "http2Session" || p.kind === "netSocket", "use (session, socket) or ()", [HTTP2SESSION_T, NETSOCKET_T]);
1386
+ return { kind: "libCall", fn: "http2.sessionOnConnect", args: [receiver(), cb, once], type: VOID, loc };
1387
+ }
1388
+ if (event === "stream") {
1389
+ const cb = h2HeadersCallbackAdapter(L, args[1], "stream listeners", "http2Stream", false, loc);
1390
+ return { kind: "libCall", fn: "http2.sessionOnStream", args: [receiver(), cb, once], type: VOID, loc };
1391
+ }
1392
+ if (event === "goaway") {
1393
+ const cb = L.lowerExpr(args[1]);
1394
+ if (cb.type.kind !== "func" || cb.type.ret.kind !== "void" || cb.type.params.length > 3) {
1395
+ L.unsupported("SC1090", args[1], "goaway listeners with more than three parameters or returning a value");
1396
+ }
1397
+ if (cb.type.params.some((p) => p.kind !== "f64" && p.kind !== "bytes")) {
1398
+ L.unsupported("SC1090", args[1], "goaway listeners whose parameters are not (errorCode, lastStreamID, opaqueData?)");
1399
+ }
1400
+ return { kind: "libCall", fn: "http2.sessionOnGoaway", args: [receiver(), voidizedCallback(L, cb, loc), once], type: VOID, loc };
1401
+ }
1402
+ L.noLowering(`session.${name}("${event ?? "?"}", ...)`, args[0], '"close", "error", "connect", "stream", and "goaway" are the supported session events');
1403
+ }
1404
+ L.noLowering(`session.${name}`, call, "close/destroy/request and on(\"close\"|\"error\"|\"connect\"|\"stream\"|\"goaway\") are the lowered session members");
1405
+ }
1406
+ /** Method calls on ClientHttp2Stream / ServerHttp2Stream receivers. */
1407
+ function lowerH2StreamMethodCall(L, call, access) {
1408
+ if (L.mapTypeOf(L.typeOf(access.expression))?.kind !== "http2Stream")
1409
+ return null;
1410
+ if (!L.isStdlibMember(access))
1411
+ return null;
1412
+ const name = access.name.text;
1413
+ const loc = locOf(call);
1414
+ const args = call.arguments;
1415
+ const receiver = () => handleReceiver(L, access.expression, HTTP2STREAM_T);
1416
+ if (name === "respond") {
1417
+ requireStatementPosition(L, call, "stream.respond(...)");
1418
+ const headersArg = args.length >= 1
1419
+ ? lowerH2HeadersArg(L, args[0])
1420
+ : { kind: "arrayLit", elems: [], type: arrayOf(STRING), loc };
1421
+ const end = h2EndStreamOption(L, args[1]);
1422
+ return { kind: "libCall", fn: "http2.streamRespond", args: [receiver(), headersArg, boolLit(end ?? false, loc)], type: VOID, loc };
1423
+ }
1424
+ if (name === "write" || name === "end") {
1425
+ requireStatementPosition(L, call, `stream.${name}(...)`);
1426
+ const minArgs = name === "write" ? 1 : 0;
1427
+ if (args.length < minArgs || args.length > 1) {
1428
+ // end() with a trailing callback is common; accept (data?, cb?) by
1429
+ // dropping a trailing closure arg (fire-and-forget — the finish
1430
+ // callback is not modeled here).
1431
+ if (!(name === "end" && args.length === 2)) {
1432
+ L.noLowering(`${name} with ${args.length} arguments`, call, `use ${name}(data${name === "write" ? "" : "?"})`);
1433
+ }
1434
+ }
1435
+ if (args.length === 0) {
1436
+ return { kind: "libCall", fn: "http2.streamEnd", args: [receiver()], type: VOID, loc };
1437
+ }
1438
+ const data = L.lowerExpr(args[0]);
1439
+ const isBytes = data.type.kind === "bytes";
1440
+ if (data.type.kind !== "string" && !isBytes) {
1441
+ L.noLowering(`${name} with '${L.fmt(data.type)}' data`, args[0], "the chunk is a string or a Uint8Array here");
1442
+ }
1443
+ const fn = name === "write"
1444
+ ? (isBytes ? "http2.streamWriteBytes" : "http2.streamWrite")
1445
+ : (isBytes ? "http2.streamEndBytes" : "http2.streamEndStr");
1446
+ return { kind: "libCall", fn, args: [receiver(), data], type: VOID, loc };
1447
+ }
1448
+ if (name === "close") {
1449
+ requireStatementPosition(L, call, "stream.close(...)");
1450
+ const code = args.length >= 1 ? L.lowerExpr(args[0]) : { kind: "numLit", value: 0, type: F64, loc };
1451
+ if (code.type.kind !== "f64") {
1452
+ L.noLowering("stream.close with a non-numeric code", args[0], "the code is a number (http2.constants.NGHTTP2_*)");
1453
+ }
1454
+ if (args.length >= 2) {
1455
+ const { cb } = lowerCallbackArg(L, args[1], "close callbacks", 0, () => false, "use ()", []);
1456
+ return { kind: "libCall", fn: "http2.streamCloseCb", args: [receiver(), code, cb], type: VOID, loc };
1457
+ }
1458
+ return { kind: "libCall", fn: "http2.streamClose", args: [receiver(), code], type: VOID, loc };
1459
+ }
1460
+ if (name === "destroy") {
1461
+ requireStatementPosition(L, call, "stream.destroy(...)");
1462
+ return { kind: "libCall", fn: "http2.streamDestroy", args: [receiver()], type: VOID, loc };
1463
+ }
1464
+ if (name === "setEncoding") {
1465
+ const enc = L.lowerExpr(args[0]);
1466
+ if (enc.type.kind !== "string")
1467
+ L.noLowering("setEncoding with a non-string encoding", args[0], "pass \"utf8\"");
1468
+ if (ts.isExpressionStatement(call.parent) || ts.isArrowFunction(call.parent)) {
1469
+ return { kind: "libCall", fn: "http2.streamSetEncoding", args: [receiver(), enc], type: VOID, loc };
1470
+ }
1471
+ // The chaining spelling (`client.request(h).setEncoding("utf8")`):
1472
+ // same write, the result is the receiver — Node's return-this.
1473
+ return { kind: "libCall", fn: "http2.streamSetEncodingRet", args: [receiver(), enc], type: HTTP2STREAM_T, loc };
1474
+ }
1475
+ if (name === "resume" || name === "pause") {
1476
+ requireStatementPosition(L, call, `stream.${name}(...)`);
1477
+ return { kind: "libCall", fn: name === "resume" ? "http2.streamResume" : "http2.streamPause", args: [receiver()], type: VOID, loc };
1478
+ }
1479
+ if ((name === "on" || name === "once" || name === "addListener") && args.length === 2) {
1480
+ requireStatementPosition(L, call, `stream.${name}(...)`);
1481
+ const once = boolLit(name === "once", loc);
1482
+ const evT = L.typeOf(args[0]);
1483
+ const event = evT.isStringLiteralType() ? evT.value : null;
1484
+ if (event === "data") {
1485
+ const { cb } = lowerCallbackArg(L, args[1], "data listeners", 1, (p) => (p.kind === "bytes" && p.elem === "u8") || p.kind === "string" || p.kind === "dyn", "use (chunk) or ()", [BYTES_U8]);
1486
+ return { kind: "libCall", fn: "http2.streamOnData", args: [receiver(), cb, once], type: VOID, loc };
1487
+ }
1488
+ if (event === "end" || event === "close" || event === "aborted") {
1489
+ const { cb } = lowerCallbackArg(L, args[1], `${event} listeners`, 0, () => false, "use ()", []);
1490
+ const fn = event === "end" ? "http2.streamOnEnd" : event === "close" ? "http2.streamOnClose" : "http2.streamOnAborted";
1491
+ return { kind: "libCall", fn, args: [receiver(), cb, once], type: VOID, loc };
1492
+ }
1493
+ if (event === "error") {
1494
+ const { cb } = lowerCallbackArg(L, args[1], "error listeners", 1, (p) => p.kind === "object" && p.className === "%Error", "use (err) or ()", [ERROR_T]);
1495
+ return { kind: "libCall", fn: "http2.streamOnError", args: [receiver(), cb, once], type: VOID, loc };
1496
+ }
1497
+ if (event === "response") {
1498
+ const cb = h2HeadersCallbackAdapter(L, args[1], "response listeners", null, true, loc);
1499
+ return { kind: "libCall", fn: "http2.streamOnResponse", args: [receiver(), cb, once], type: VOID, loc };
1500
+ }
1501
+ L.noLowering(`stream.${name}("${event ?? "?"}", ...)`, args[0], '"data", "end", "close", "aborted", "error", and "response" are the supported stream events');
1502
+ }
1503
+ L.noLowering(`stream.${name}`, call, "respond/write/end/close/destroy/setEncoding/resume/pause and on(...) are the lowered stream members");
1504
+ }
1505
+ /** The composed `server.address().port` read — the crypto
1506
+ * randomBytes(n).toString(enc) precedent: the AddressInfo record between
1507
+ * the two reads never materializes; the runtime answers the bound port
1508
+ * directly. Null for every other property shape. */
1509
+ export function lowerServerProperty(L, expr) {
1510
+ if (expr.questionDotToken)
1511
+ return null;
1512
+ const loc = locOf(expr);
1513
+ // req.url / req.method — always-present strings on server requests;
1514
+ // req.headers.NAME — the `string | undefined` union (envGet's type).
1515
+ const recvKind = L.mapTypeOf(L.typeOf(expr.expression))?.kind;
1516
+ if (recvKind === "httpReq" && L.isStdlibMember(expr)) {
1517
+ const name = expr.name.text;
1518
+ if (name === "url" || name === "method") {
1519
+ const receiver = handleReceiver(L, expr.expression, HTTPREQ_T);
1520
+ const fn = name === "url" ? "http.reqUrl" : "http.reqMethod";
1521
+ return { kind: "libCall", fn, args: [receiver], type: STRING, loc };
1522
+ }
1523
+ if (name === "statusCode") {
1524
+ // `number | undefined` — a real status on client responses, the
1525
+ // undefined arm on server requests (Node's IncomingMessage split).
1526
+ const receiver = handleReceiver(L, expr.expression, HTTPREQ_T);
1527
+ return { kind: "libCall", fn: "http.reqStatusCode", args: [receiver], type: L.withUndefinedArm(F64), loc };
1528
+ }
1529
+ if (name === "socket") {
1530
+ const receiver = handleReceiver(L, expr.expression, HTTPREQ_T);
1531
+ return { kind: "libCall", fn: "http.reqSocket", args: [receiver], type: NETSOCKET_T, loc };
1532
+ }
1533
+ if (name === "httpVersion") {
1534
+ const receiver = handleReceiver(L, expr.expression, HTTPREQ_T);
1535
+ return { kind: "libCall", fn: "http.reqHttpVersion", args: [receiver], type: STRING, loc };
1536
+ }
1537
+ if (name === "httpVersionMajor" || name === "httpVersionMinor") {
1538
+ const receiver = handleReceiver(L, expr.expression, HTTPREQ_T);
1539
+ const fn = name === "httpVersionMajor" ? "http.reqHttpVersionMajor" : "http.reqHttpVersionMinor";
1540
+ return { kind: "libCall", fn, args: [receiver], type: F64, loc };
1541
+ }
1542
+ if (name === "aborted" || name === "complete") {
1543
+ // The compat pair's flags (Http2ServerRequest); an http/1 request
1544
+ // answers aborted: false and complete-once-ended the same way.
1545
+ const receiver = handleReceiver(L, expr.expression, HTTPREQ_T);
1546
+ const fn = name === "aborted" ? "http.reqAborted" : "http.reqComplete";
1547
+ return { kind: "libCall", fn, args: [receiver], type: BOOL, loc };
1548
+ }
1549
+ if (name === "rawHeaders") {
1550
+ // [name, value, name, value, ...] in arrival order, names in their
1551
+ // ORIGINAL case — Node's shape; a fresh string[] per read.
1552
+ const receiver = handleReceiver(L, expr.expression, HTTPREQ_T);
1553
+ return { kind: "libCall", fn: "http.reqRawHeaders", args: [receiver], type: arrayOf(STRING), loc };
1554
+ }
1555
+ if (name === "statusMessage") {
1556
+ // `string | undefined` — the reason phrase on client responses (""
1557
+ // when the status line carried none), undefined on server requests
1558
+ // (the statusCode split).
1559
+ const receiver = handleReceiver(L, expr.expression, HTTPREQ_T);
1560
+ return { kind: "libCall", fn: "http.reqStatusMessage", args: [receiver], type: L.envValueType(), loc };
1561
+ }
1562
+ if (name === "stream" || name === "session") {
1563
+ // Http2ServerRequest's h2-only members. On the allowHTTP1 lowering
1564
+ // every connection is HTTP/1.1, where Node answers undefined for
1565
+ // both — the CALL forms lower in lower-calls.ts (guarded ?. no-ops,
1566
+ // unguarded throws Node's exact TypeError); a bare read in any
1567
+ // other position has no undefined-typed value form here, so the
1568
+ // fence names the compiling shapes.
1569
+ L.noLowering(`reading '${name}' (an HTTP/2-only member) from a request`, expr, "the allowHTTP1 lowering serves every connection as HTTP/1.1, where Node answers undefined — method calls compile (req.stream?.on(...) no-ops; unguarded req.stream.on(...) throws Node's TypeError); other reads have no lowering");
1570
+ }
1571
+ if (name === "headers") {
1572
+ // `req.headers` as a VALUE — the `{ ...req.headers }` spread and
1573
+ // record flows: a fresh snapshot record per read (Node's spread
1574
+ // copies too; the per-name READS keep their direct lowerings).
1575
+ const mapped = L.mapTypeOf(L.typeOf(expr));
1576
+ if (mapped?.kind === "record") {
1577
+ const helper = headersSnapshotHelper(L, mapped.shapeId, loc);
1578
+ if (helper !== null) {
1579
+ const receiver = handleReceiver(L, expr.expression, HTTPREQ_T);
1580
+ return { kind: "call", callee: helper, args: [receiver], type: mapped, loc };
1581
+ }
1582
+ }
1583
+ L.unsupported("SC1090", expr, "req.headers as a value of this type (read one header: req.headers.name or req.headers[name])");
1584
+ }
1585
+ }
1586
+ if (recvKind === "httpRes" && L.isStdlibMember(expr) && expr.name.text === "stream") {
1587
+ L.noLowering("reading 'stream' (an HTTP/2-only member) from a response", expr, "the allowHTTP1 lowering serves every connection as HTTP/1.1, where Node answers undefined — guard method calls with '?.' or drop the use");
1588
+ }
1589
+ if (recvKind === "netSocket" && expr.name.text === "encrypted") {
1590
+ // `boolean | undefined` — Node types `encrypted` on TLSSocket only
1591
+ // (true from construction); reading it off a plain socket answers
1592
+ // undefined. The @types/node idiom reaches it through a cast
1593
+ // (`socket as net.Socket & { encrypted?: boolean }` — proxy.ts's
1594
+ // isEncrypted), so the member's declaration is USER code and the
1595
+ // stdlib-provenance check deliberately does not apply: the receiver
1596
+ // KIND is a real socket either way, and `encrypted` on a socket has
1597
+ // exactly one meaning.
1598
+ const receiver = handleReceiver(L, expr.expression, NETSOCKET_T);
1599
+ return { kind: "libCall", fn: "net.sockEncrypted", args: [receiver], type: L.withUndefinedArm(BOOL), loc };
1600
+ }
1601
+ if (recvKind === "netSocket" && expr.name.text === "authorized") {
1602
+ // TLSSocket.authorized — Node's verify verdict (false on plain
1603
+ // sockets, servers without requestCert, and unverified clients).
1604
+ // Like `encrypted`, no stdlib-provenance gate: the member has exactly
1605
+ // one meaning on a socket-kind receiver.
1606
+ const receiver = handleReceiver(L, expr.expression, NETSOCKET_T);
1607
+ return { kind: "libCall", fn: "tls.sockAuthorized", args: [receiver], type: BOOL, loc };
1608
+ }
1609
+ if (recvKind === "netSocket" && expr.name.text === "authorizationError") {
1610
+ // TLSSocket.authorizationError — the verify-failure CODE STRING
1611
+ // (DEPTH_ZERO_SELF_SIGNED_CERT, ...) or null when authorized/never
1612
+ // verified: Node's exact value shape (a string, not an Error).
1613
+ const receiver = handleReceiver(L, expr.expression, NETSOCKET_T);
1614
+ const type = { kind: "union", unionId: L.unions.intern([STRING, NULL_T]) };
1615
+ return { kind: "libCall", fn: "tls.sockAuthError", args: [receiver], type, loc };
1616
+ }
1617
+ if (recvKind === "netSocket" && L.isStdlibMember(expr) && expr.name.text === "destroyed") {
1618
+ // true once the fd is gone (destroy() or full close) — Node's flag,
1619
+ // the proxy's re-entrant teardown guard.
1620
+ const receiver = handleReceiver(L, expr.expression, NETSOCKET_T);
1621
+ return { kind: "libCall", fn: "net.sockDestroyed", args: [receiver], type: BOOL, loc };
1622
+ }
1623
+ if (recvKind === "netSocket" && L.isStdlibMember(expr) && expr.name.text === "writable") {
1624
+ // Node's stream answer: the write half is open — no end() yet, no FIN
1625
+ // sent, fd alive (connecting sockets answer true; writes queue). The
1626
+ // proxy's "may I still answer 502" guard.
1627
+ const receiver = handleReceiver(L, expr.expression, NETSOCKET_T);
1628
+ return { kind: "libCall", fn: "net.sockWritable", args: [receiver], type: BOOL, loc };
1629
+ }
1630
+ if (recvKind === "netSocket" && L.isStdlibMember(expr) && expr.name.text === "remoteAddress") {
1631
+ // `string | undefined` — Node's read-time caching: a value read while
1632
+ // connected survives destroy; never-read sockets answer undefined
1633
+ // after close.
1634
+ const receiver = handleReceiver(L, expr.expression, NETSOCKET_T);
1635
+ return { kind: "libCall", fn: "net.sockRemoteAddress", args: [receiver], type: L.envValueType(), loc };
1636
+ }
1637
+ if (recvKind === "httpClientReq" && L.isStdlibMember(expr) && expr.name.text === "destroyed") {
1638
+ const receiver = handleReceiver(L, expr.expression, HTTPCLIENTREQ_T);
1639
+ return { kind: "libCall", fn: "http.clientDestroyed", args: [receiver], type: BOOL, loc };
1640
+ }
1641
+ if (isHttpReqHeaders(L, expr.expression)) {
1642
+ const receiver = handleReceiver(L, expr.expression.expression, HTTPREQ_T);
1643
+ const key = { kind: "strLit", value: expr.name.text, type: STRING, loc };
1644
+ return { kind: "libCall", fn: "http.reqHeader", args: [receiver, key], type: L.envValueType(), loc };
1645
+ }
1646
+ if (recvKind === "httpRes" && L.isStdlibMember(expr) && expr.name.text === "headersSent") {
1647
+ const receiver = handleReceiver(L, expr.expression, HTTPRES_T);
1648
+ return { kind: "libCall", fn: "http.resHeadersSent", args: [receiver], type: BOOL, loc };
1649
+ }
1650
+ if (recvKind === "httpRes" && L.isStdlibMember(expr) && expr.name.text === "statusCode") {
1651
+ // 200 until assigned — Node's fresh-response default; the assignment
1652
+ // twin lives in lowerHttpResPropertyAssignment.
1653
+ const receiver = handleReceiver(L, expr.expression, HTTPRES_T);
1654
+ return { kind: "libCall", fn: "http.resStatusGet", args: [receiver], type: F64, loc };
1655
+ }
1656
+ if (recvKind === "httpRes" && L.isStdlibMember(expr) && expr.name.text === "statusMessage") {
1657
+ // The assigned reason phrase, or the current status code's default
1658
+ // when none was set (Node answers undefined until the head goes out
1659
+ // — divergence: this surface is string-typed, the checker's shape).
1660
+ const receiver = handleReceiver(L, expr.expression, HTTPRES_T);
1661
+ return { kind: "libCall", fn: "http.resStatusMsgGet", args: [receiver], type: STRING, loc };
1662
+ }
1663
+ if (recvKind === "http2Session" && L.isStdlibMember(expr)) {
1664
+ const m = expr.name.text;
1665
+ const recv = () => handleReceiver(L, expr.expression, HTTP2SESSION_T);
1666
+ if (m === "closed")
1667
+ return { kind: "libCall", fn: "http2.sessionClosed", args: [recv()], type: BOOL, loc };
1668
+ if (m === "destroyed")
1669
+ return { kind: "libCall", fn: "http2.sessionDestroyed", args: [recv()], type: BOOL, loc };
1670
+ if (m === "encrypted")
1671
+ return { kind: "libCall", fn: "http2.sessionEncrypted", args: [recv()], type: BOOL, loc };
1672
+ if (m === "type")
1673
+ return { kind: "libCall", fn: "http2.sessionType", args: [recv()], type: F64, loc };
1674
+ if (m === "alpnProtocol")
1675
+ return { kind: "libCall", fn: "http2.sessionAlpn", args: [recv()], type: STRING, loc };
1676
+ if (m === "socket")
1677
+ return { kind: "libCall", fn: "http2.sessionSocket", args: [recv()], type: NETSOCKET_T, loc };
1678
+ if (m === "pendingSettingsAck")
1679
+ return { kind: "libCall", fn: "http2.sessionPendingSettingsAck", args: [recv()], type: BOOL, loc };
1680
+ if (m === "localSettings" || m === "remoteSettings") {
1681
+ // The settings record crosses as a DOM value (the d.ts types it
1682
+ // `any`): member reads ride the checked-dynamic keyed read.
1683
+ return {
1684
+ kind: "libCall", fn: "http2.sessionSettingsGet",
1685
+ args: [recv(), boolLit(m === "localSettings", loc)], type: DYN, loc,
1686
+ };
1687
+ }
1688
+ }
1689
+ if (recvKind === "http2Stream" && L.isStdlibMember(expr)) {
1690
+ const m = expr.name.text;
1691
+ const recv = () => handleReceiver(L, expr.expression, HTTP2STREAM_T);
1692
+ if (m === "id")
1693
+ return { kind: "libCall", fn: "http2.streamId", args: [recv()], type: F64, loc };
1694
+ if (m === "rstCode")
1695
+ return { kind: "libCall", fn: "http2.streamRstCode", args: [recv()], type: F64, loc };
1696
+ if (m === "destroyed")
1697
+ return { kind: "libCall", fn: "http2.streamDestroyed", args: [recv()], type: BOOL, loc };
1698
+ if (m === "closed")
1699
+ return { kind: "libCall", fn: "http2.streamClosed", args: [recv()], type: BOOL, loc };
1700
+ if (m === "aborted")
1701
+ return { kind: "libCall", fn: "http2.streamAborted", args: [recv()], type: BOOL, loc };
1702
+ if (m === "pending")
1703
+ return { kind: "libCall", fn: "http2.streamPending", args: [recv()], type: BOOL, loc };
1704
+ if (m === "session")
1705
+ return { kind: "libCall", fn: "http2.streamSession", args: [recv()], type: HTTP2SESSION_T, loc };
1706
+ }
1707
+ if (expr.name.text !== "port")
1708
+ return null;
1709
+ const recv = expr.expression;
1710
+ if (!ts.isCallExpression(recv) || recv.questionDotToken || recv.arguments.length !== 0)
1711
+ return null;
1712
+ if (!ts.isPropertyAccessExpression(recv.expression))
1713
+ return null;
1714
+ const inner = recv.expression;
1715
+ if (inner.name.text !== "address" || inner.questionDotToken)
1716
+ return null;
1717
+ if (L.mapTypeOf(L.typeOf(inner.expression))?.kind !== "netServer")
1718
+ return null;
1719
+ if (!L.isStdlibMember(inner))
1720
+ return null;
1721
+ const receiver = handleReceiver(L, inner.expression, NETSERVER_T);
1722
+ return { kind: "libCall", fn: "net.serverPort", args: [receiver], type: F64, loc };
1723
+ }
1724
+ /** The (req, res) request-handler argument — http.createServer's, https
1725
+ * createServer's, http.Server's, and server.on("request")'s shared shape
1726
+ * check: at most two parameters typed (IncomingMessage, ServerResponse).
1727
+ * A value-returning handler adapts through the cast-and-discard helper
1728
+ * (Node ignores handler results — the writeHead-chaining body
1729
+ * `(req, res) => res.writeHead(200).end()` is the motivating shape). */
1730
+ function lowerRequestHandlerArg(L, node) {
1731
+ const cb = L.lowerExpr(node);
1732
+ if (cb.type.kind === "dyn") {
1733
+ // A CHECKED-DYNAMIC handler (test/common's mustCall wrapper around
1734
+ // the (req, res) listener — the canonical suite shape): adapt
1735
+ // through the dynCheck function boundary. The adapter boxes req/res
1736
+ // as DOM HANDLE values (SCR_DYN_HANDLE — reference identity) and
1737
+ // calls the wrapper through the checked-dynamic machinery; member
1738
+ // uses inside the wrapped body dispatch back onto the same http
1739
+ // entry points at runtime.
1740
+ return { kind: "dynCheck", value: cb, type: funcOf([HTTPREQ_T, HTTPRES_T], VOID), loc: locOf(node) };
1741
+ }
1742
+ if (cb.type.kind === "func" &&
1743
+ (cb.type.rest === true ||
1744
+ (cb.type.params.length > 0 && cb.type.params.every((p) => p.kind === "dyn"))) &&
1745
+ canBoxFuncIntoDyn(cb.type, (id) => L.shapes.get(id), (id) => L.unions.get(id))) {
1746
+ // A hoisted plain-JS handler (`http.createServer(handle)` where
1747
+ // `function handle(req, res)` has no contextual type — dyn params),
1748
+ // or an arguments-reading rest function: box it and adapt exactly
1749
+ // like the dyn case — req/res arrive as HANDLE boxes and the body's
1750
+ // member uses dispatch at runtime.
1751
+ const boxed = { kind: "dynFrom", value: cb, type: DYN, loc: locOf(node) };
1752
+ return { kind: "dynCheck", value: boxed, type: funcOf([HTTPREQ_T, HTTPRES_T], VOID), loc: locOf(node) };
1753
+ }
1754
+ if (cb.type.kind !== "func" || cb.type.params.length > 2) {
1755
+ L.unsupported("SC1090", node, "request handlers with more than two parameters (use (req, res))");
1756
+ }
1757
+ const [p0, p1] = cb.type.params;
1758
+ if ((p0 !== undefined && p0.kind !== "httpReq") || (p1 !== undefined && p1.kind !== "httpRes")) {
1759
+ L.unsupported("SC1090", node, "request handlers whose parameters are not (req: IncomingMessage, res: ServerResponse)");
1760
+ }
1761
+ return voidizedCallback(L, cb, locOf(node));
1762
+ }
1763
+ /** The http.createServer / http.Server options object — { requireHostHeader?,
1764
+ * joinDuplicateHeaders?, ... }: the two lowered keys are exactly the
1765
+ * parser behaviors this runtime has (requireHostHeader: false IS the
1766
+ * parser's stance — it never answers 400 for a missing Host;
1767
+ * joinDuplicateHeaders: true joins repeated request-header reads with
1768
+ * ", "). Other documented keys fence by name; unknown keys drop like
1769
+ * Node drops them. Answers whether joinDuplicateHeaders was requested. */
1770
+ function lowerHttpServerOptions(L, node, what) {
1771
+ if (!ts.isObjectLiteralExpression(node)) {
1772
+ L.noLowering(`${what} with a non-literal options argument`, node, "pass the options as an object literal: { requireHostHeader?, joinDuplicateHeaders? }");
1773
+ }
1774
+ let joinDup = false;
1775
+ for (const prop of node.properties) {
1776
+ let initializer;
1777
+ if (ts.isPropertyAssignment(prop) &&
1778
+ (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) {
1779
+ initializer = prop.initializer;
1780
+ }
1781
+ else if (ts.isShorthandPropertyAssignment(prop)) {
1782
+ initializer = null;
1783
+ }
1784
+ else {
1785
+ L.noLowering(`${what} options with computed keys or spreads`, prop, "each option must be a plain `name: value` (or shorthand) entry with a literal key");
1786
+ }
1787
+ const key = prop.name.text;
1788
+ if (key === "requireHostHeader") {
1789
+ // The literal `false` only: it asks for exactly this parser's
1790
+ // behavior (requests without Host are served). Node's default
1791
+ // (true: answer 400) is the behavior this slice does not have, so
1792
+ // `true`/dynamic values fence instead of silently not enforcing.
1793
+ if (initializer === null || initializer.kind !== ts.SyntaxKind.FalseKeyword) {
1794
+ L.noLowering(`${what} with a non-\`false\` requireHostHeader option`, prop, "this parser never answers 400 for a missing Host header — requireHostHeader: false is the honest (and only) lowered value");
1795
+ }
1796
+ continue;
1797
+ }
1798
+ if (key === "joinDuplicateHeaders") {
1799
+ // Literal true/false: the flag decides parse-time read semantics,
1800
+ // so a runtime value would silently pick one — fence dynamics.
1801
+ if (initializer !== null && initializer.kind === ts.SyntaxKind.TrueKeyword) {
1802
+ joinDup = true;
1803
+ continue;
1804
+ }
1805
+ if (initializer !== null && initializer.kind === ts.SyntaxKind.FalseKeyword) {
1806
+ continue; /* Node's default: repeats keep the first value */
1807
+ }
1808
+ L.noLowering(`${what} with a non-literal joinDuplicateHeaders option`, prop, "the lowered forms are the literals joinDuplicateHeaders: true (repeats join \", \") and false (the keep-first default)");
1809
+ }
1810
+ fenceOrDropOptionKey(L, prop, key, what, HTTP_SERVER_DOCUMENTED_OPTIONS, "requireHostHeader: false and joinDuplicateHeaders are the supported options");
1811
+ // An undocumented key, dropped like Node drops it.
1812
+ }
1813
+ return { joinDup };
1814
+ }
1815
+ /** The shared createServer([options][, handler]) shapes behind
1816
+ * http.createServer, http.Server, and `new http.Server`: no arguments
1817
+ * (the on("request") route), a lone handler, a lone options object, and
1818
+ * (options, handler). joinDuplicateHeaders wraps the construction in the
1819
+ * flag-setting helper. */
1820
+ function lowerHttpCreateServerForms(L, expr, what, loc) {
1821
+ const args = expr.arguments ?? [];
1822
+ if (args.length > 2) {
1823
+ L.noLowering(`${what} with ${args.length} arguments`, expr, `the supported forms are ${what}([options][, (req, res) => ...])`);
1824
+ }
1825
+ let optsNode = null;
1826
+ let handlerNode = null;
1827
+ if (args.length === 2) {
1828
+ optsNode = args[0];
1829
+ handlerNode = args[1];
1830
+ }
1831
+ else if (args.length === 1) {
1832
+ if (ts.isObjectLiteralExpression(args[0]))
1833
+ optsNode = args[0];
1834
+ else
1835
+ handlerNode = args[0];
1836
+ }
1837
+ const joinDup = optsNode !== null && lowerHttpServerOptions(L, optsNode, what).joinDup;
1838
+ const server = handlerNode !== null
1839
+ ? { kind: "libCall", fn: "http.createServer", args: [lowerRequestHandlerArg(L, handlerNode)], type: NETSERVER_T, loc }
1840
+ : { kind: "libCall", fn: "http.createServerEmpty", args: [], type: NETSERVER_T, loc };
1841
+ if (!joinDup)
1842
+ return server;
1843
+ // The flag-setting composition: an interned helper takes the fresh
1844
+ // server, sets the parser flag, and answers it — one expression.
1845
+ const key = `server.joindup`;
1846
+ const existing = L.arrHofHelpers.get(key);
1847
+ const name = existing ?? `%server.joindup.${L.arrHofHelpers.size}`;
1848
+ if (!existing) {
1849
+ L.arrHofHelpers.set(key, name);
1850
+ const ref = { kind: "varRef", localId: "s.0", type: NETSERVER_T, loc };
1851
+ L.liftedFns.push({
1852
+ name,
1853
+ params: [{ localId: "s.0", name: "s", type: NETSERVER_T }],
1854
+ returnType: NETSERVER_T,
1855
+ locals: [{ id: "s.0", name: "s", type: NETSERVER_T, mutable: false }],
1856
+ body: [
1857
+ {
1858
+ kind: "exprStmt",
1859
+ expr: { kind: "libCall", fn: "http.serverJoinDupHeaders", args: [ref], type: VOID, loc },
1860
+ loc,
1861
+ },
1862
+ { kind: "return", value: ref, loc },
1863
+ ],
1864
+ loc,
1865
+ });
1866
+ }
1867
+ return { kind: "call", callee: name, args: [server], type: NETSERVER_T, loc };
1868
+ }
1869
+ /** `new http.Server([options][, handler])` — the constructor spelling of
1870
+ * createServer (Node's Server class IS the factory's product); called
1871
+ * from lowerNew ahead of the stdlib-constructor fence. Null when the
1872
+ * callee is not the http module's Server. */
1873
+ export function lowerHttpServerNew(L, expr) {
1874
+ const callee = expr.expression;
1875
+ const isHttpServer = (ts.isPropertyAccessExpression(callee) &&
1876
+ callee.name.text === "Server" &&
1877
+ L.builtinMemberOf(callee)?.module === "http") ||
1878
+ (ts.isIdentifier(callee) &&
1879
+ L.builtinImportOf(callee)?.module === "http" &&
1880
+ L.builtinImportOf(callee)?.member === "Server");
1881
+ if (!isHttpServer)
1882
+ return null;
1883
+ return lowerHttpCreateServerForms(L, expr, "new http.Server", locOf(expr));
1884
+ }
1885
+ /** Module-function calls on http import bindings: createServer([options][,
1886
+ * handler]) and the call-form Server (Node's constructor works without
1887
+ * `new` — test/parallel's http.Server(fn) spelling). The handler takes
1888
+ * (req, res), (req), or () — runtime adapters bridge each shape.
1889
+ * Everything else the module declares fences qualified. */
1890
+ function lowerHttpModuleCall(L, expr, bi, loc) {
1891
+ if (bi.member === "createServer" || bi.member === "Server") {
1892
+ return lowerHttpCreateServerForms(L, expr, `http.${bi.member}`, loc);
1893
+ }
1894
+ if (bi.member === "request" || bi.member === "get") {
1895
+ return lowerHttpClientCall(L, expr, bi.member, loc);
1896
+ }
1897
+ L.noLowering(`http.${bi.member}`, expr, "createServer, Server, request, and get are the lowered http module functions", L.resolveValueSymbol(expr.expression));
1898
+ }
1899
+ /** The cert/key PEM option of tls.createServer / https.createServer: an
1900
+ * OBJECT LITERAL whose lowered `cert` and `key` entries are strings or
1901
+ * Buffers (fs.readFileSync either way — portless reads Buffers) — a
1902
+ * runtime-valued (dyn) entry rides the tls.pemDyn extraction, throwing
1903
+ * the fence at runtime for non-PEM kinds (the divergence-66 stance).
1904
+ * Every other key fences by name; SNICallback gets the pointed hint. */
1905
+ function lowerTlsServerOptions(L, node, what) {
1906
+ if (!ts.isObjectLiteralExpression(node)) {
1907
+ L.noLowering(`${what} with a non-literal options argument`, node, "pass the options as an object literal: { cert, key }");
1908
+ }
1909
+ const loc = locOf(node);
1910
+ let cert = null;
1911
+ let key = null;
1912
+ for (const prop of node.properties) {
1913
+ let initializer;
1914
+ if (ts.isPropertyAssignment(prop) &&
1915
+ (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) {
1916
+ initializer = prop.initializer;
1917
+ }
1918
+ else if (ts.isShorthandPropertyAssignment(prop)) {
1919
+ initializer = null;
1920
+ }
1921
+ else {
1922
+ L.noLowering(`${what} options with computed keys or spreads`, prop, "each option must be a plain `name: value` (or shorthand) entry with a literal key");
1923
+ }
1924
+ const k = prop.name.text;
1925
+ if (k !== "cert" && k !== "key") {
1926
+ const init = ts.isPropertyAssignment(prop) ? prop.initializer : null;
1927
+ if ((k === "ca" || k === "rejectUnauthorized") &&
1928
+ (init === null || sideEffectFreeOptionValue(init))) {
1929
+ // Both are inert without requestCert (which fences): the server's
1930
+ // `ca` verifies CLIENT certificates and rejectUnauthorized only
1931
+ // gates that verification — Node-observably droppable, matching
1932
+ // the runtime walk's split (effectful values keep the fence).
1933
+ continue;
1934
+ }
1935
+ if (k === "requestCert" &&
1936
+ (init === null || init.kind === ts.SyntaxKind.FalseKeyword)) {
1937
+ continue; // Node's false default, spelled out
1938
+ }
1939
+ fenceOrDropOptionKey(L, prop, k, what, TLS_SERVER_DOCUMENTED_OPTIONS, "cert and key (PEM strings or Buffers) are the supported options", {
1940
+ SNICallback: "SNICallback lowers only on http2.createSecureServer({ allowHTTP1: true, ... }) — serve one cert/key pair here",
1941
+ requestCert: "client-certificate handshakes are not modeled (SEMANTICS.md divergence 55) — requestCert: false is the lowered value",
1942
+ });
1943
+ continue; // an undocumented key, dropped like Node drops it
1944
+ }
1945
+ let v = initializer !== null
1946
+ ? L.lowerExpr(initializer)
1947
+ : L.lowerShorthandValue(prop);
1948
+ const isArrayOfPem = v.type.kind === "array" &&
1949
+ (v.type.elem.kind === "string" || (v.type.elem.kind === "bytes" && v.type.elem.elem === "u8"));
1950
+ if (v.type.kind === "dyn" || isArrayOfPem) {
1951
+ // A runtime PEM value (fixtures.readKey(...) — the suite's shape) or
1952
+ // the one-element-array multi-context spelling: the runtime
1953
+ // extraction accepts strings/Buffers (and one-element arrays of
1954
+ // those) and throws the catchable fence otherwise.
1955
+ v = {
1956
+ kind: "libCall",
1957
+ fn: "tls.pemDyn",
1958
+ args: [
1959
+ v.type.kind === "dyn" ? v : { kind: "dynFrom", value: v, type: DYN, loc },
1960
+ { kind: "strLit", value: `a ${what} '${k}' option`, type: STRING, loc },
1961
+ ],
1962
+ type: BYTES_U8,
1963
+ loc,
1964
+ };
1965
+ }
1966
+ else if (v.type.kind !== "string" && !(v.type.kind === "bytes" && v.type.elem === "u8")) {
1967
+ L.noLowering(`a ${what} '${k}' option of '${L.fmt(v.type)}' values`, prop, "cert/key are PEM strings or Buffers here");
1968
+ }
1969
+ if (k === "cert")
1970
+ cert = v;
1971
+ else
1972
+ key = v;
1973
+ }
1974
+ if (cert === null || key === null) {
1975
+ L.noLowering(`${what} without both cert and key`, node, "the supported options object is { cert, key } (PEM strings or Buffers)");
1976
+ }
1977
+ return { cert, key };
1978
+ }
1979
+ /** The options argument of tls/https createServer, split three ways: an
1980
+ * object literal takes the static walk above; a RUNTIME (dyn) value — the
1981
+ * checked-dynamic JS lane's record — passes whole to the runtime walk
1982
+ * (scr_tls_srv_opts_walk: same member split, fences thrown at runtime,
1983
+ * the divergence-66 stance); anything else keeps the compile fence. */
1984
+ function lowerTlsServerOptionsOrDyn(L, node, what) {
1985
+ if (ts.isObjectLiteralExpression(node))
1986
+ return lowerTlsServerOptions(L, node, what);
1987
+ const v = L.lowerExpr(node);
1988
+ if (v.type.kind === "dyn")
1989
+ return { dyn: v };
1990
+ // A typed options RECORD binding (`const options = { key, cert, ... };
1991
+ // createServer(options)` — the Node-suite spelling): box it into the
1992
+ // DOM and hand the whole record to the runtime walk (the divergence-66
1993
+ // stance — members read at runtime, out-of-bounds ones throw the
1994
+ // catchable fence, undefined/undocumented ones drop). canConvertToDyn
1995
+ // now folds in the bytes-bearing option records the walker can box.
1996
+ if (canConvertToDyn(v.type, (id) => L.shapes.get(id), (id) => L.unions.get(id))) {
1997
+ return { dyn: { kind: "dynFrom", value: v, type: DYN, loc: locOf(node) } };
1998
+ }
1999
+ L.noLowering(`${what} with a non-literal options argument`, node, "pass the options as an object literal: { cert, key }");
2000
+ }
2001
+ /** tls.connect — the TLS client socket. The lowered forms are
2002
+ * connect(options[, cb]) and connect(port[, host][, options][, cb]) with
2003
+ * a RUNTIME options record (the checked-dynamic JS lane's shape — a dyn
2004
+ * value, or a record that converts): the runtime walk implements
2005
+ * port/host/rejectUnauthorized/ca/servername and throws the catchable
2006
+ * fence for other documented members (the divergence-66 stance). The
2007
+ * callback (and 'secureConnect'/'connect' listeners) fires
2008
+ * post-handshake — Node's secureConnect timing. */
2009
+ function lowerTlsConnectCall(L, expr, loc) {
2010
+ const args = expr.arguments;
2011
+ const FORMS_HINT = "the supported forms are connect(options[, cb]) and connect(port[, host][, options][, cb]) — options implement port/host/rejectUnauthorized/ca/servername";
2012
+ if (args.length < 1 || args.length > 4 || args.some(ts.isSpreadElement)) {
2013
+ L.noLowering(`tls.connect with ${args.length} arguments`, expr, FORMS_HINT);
2014
+ }
2015
+ const isFuncish = (a) => ts.isFunctionExpression(a) || ts.isArrowFunction(a) ||
2016
+ L.checker.getCallSignatures(L.typeOf(a)).length > 0;
2017
+ const isObjectish = (a) => {
2018
+ if (ts.isObjectLiteralExpression(a))
2019
+ return true;
2020
+ if (isFuncish(a) || ts.isStringLiteralLike(a) || ts.isNumericLiteral(a))
2021
+ return false;
2022
+ const t = L.typeOf(a);
2023
+ return (t.flags & ts.TypeFlags.Object) !== 0;
2024
+ };
2025
+ const num = (value) => ({ kind: "numLit", value, type: F64, loc });
2026
+ let port = num(-1); // -1: the runtime reads options.port
2027
+ let host = { kind: "strLit", value: "", type: STRING, loc }; // "": options.host / localhost
2028
+ let optsNode = null;
2029
+ let i = 0;
2030
+ if (isObjectish(args[0])) {
2031
+ optsNode = args[0];
2032
+ i = 1;
2033
+ }
2034
+ else if (isFuncish(args[0])) {
2035
+ L.noLowering("tls.connect with a callback-first argument shape", expr, FORMS_HINT);
2036
+ }
2037
+ else {
2038
+ const p = L.lowerExpr(args[0]);
2039
+ if (p.type.kind === "f64")
2040
+ port = p;
2041
+ else if (p.type.kind === "dyn")
2042
+ port = { kind: "dynCheck", value: p, type: F64, loc };
2043
+ else
2044
+ L.noLowering(`tls.connect with a '${L.fmt(p.type)}' port`, args[0], FORMS_HINT);
2045
+ i = 1;
2046
+ if (i < args.length && !isFuncish(args[i]) && !isObjectish(args[i])) {
2047
+ const h = L.lowerExpr(args[i]);
2048
+ if (h.type.kind === "string")
2049
+ host = h;
2050
+ else if (h.type.kind === "dyn")
2051
+ host = { kind: "dynCheck", value: h, type: STRING, loc };
2052
+ else
2053
+ L.noLowering(`tls.connect with a '${L.fmt(h.type)}' host`, args[i], FORMS_HINT);
2054
+ i++;
2055
+ }
2056
+ if (i < args.length && isObjectish(args[i])) {
2057
+ optsNode = args[i];
2058
+ i++;
2059
+ }
2060
+ }
2061
+ // The options value rides whole to the runtime walk: dyn passes
2062
+ // directly; a typed record that converts boxes into the DOM.
2063
+ let opts;
2064
+ if (optsNode !== null) {
2065
+ const o = L.lowerExpr(optsNode);
2066
+ if (o.type.kind === "dyn") {
2067
+ opts = o;
2068
+ }
2069
+ else if (canConvertToDyn(o.type, (id) => L.shapes.get(id), (id) => L.unions.get(id))) {
2070
+ opts = { kind: "dynFrom", value: o, type: DYN, loc };
2071
+ }
2072
+ else {
2073
+ L.noLowering(`tls.connect with a '${L.fmt(o.type)}' options record`, optsNode, FORMS_HINT);
2074
+ }
2075
+ }
2076
+ else {
2077
+ if (port.kind === "numLit" && port.value === -1) {
2078
+ L.noLowering("tls.connect without a port or options", expr, FORMS_HINT);
2079
+ }
2080
+ // No options: Node's defaults (rejectUnauthorized: true) — the
2081
+ // runtime walk reads an absent record (the DOM undefined).
2082
+ opts = {
2083
+ kind: "dynFrom",
2084
+ value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc },
2085
+ type: DYN,
2086
+ loc,
2087
+ };
2088
+ }
2089
+ let cb = null;
2090
+ if (i < args.length) {
2091
+ if (i !== args.length - 1 || !isFuncish(args[i])) {
2092
+ L.noLowering("tls.connect with this argument shape", expr, FORMS_HINT);
2093
+ }
2094
+ const r = lowerCallbackArg(L, args[i], "secureConnect listeners", 0, () => false, "use ()", []);
2095
+ cb = r.cb;
2096
+ }
2097
+ return cb !== null
2098
+ ? { kind: "libCall", fn: "tls.connectCb", args: [port, host, opts, cb], type: NETSOCKET_T, loc }
2099
+ : { kind: "libCall", fn: "tls.connect", args: [port, host, opts], type: NETSOCKET_T, loc };
2100
+ }
2101
+ /** Module-function calls on tls import bindings: createServer(options[,
2102
+ * secureConnectionListener]) — the listener fires post-handshake with a
2103
+ * socket that behaves exactly like a net socket. Everything else the
2104
+ * module declares fences qualified (createSecureContext and connect
2105
+ * carry pointed hints from the fence-hint table). */
2106
+ function lowerTlsModuleCall(L, expr, bi, loc) {
2107
+ const args = expr.arguments;
2108
+ if (bi.member === "createServer") {
2109
+ if (args.length < 1 || args.length > 2) {
2110
+ L.noLowering(`createServer with ${args.length} arguments`, expr, "the supported form is createServer({ cert, key }, socket => ...)");
2111
+ }
2112
+ const opts = lowerTlsServerOptionsOrDyn(L, args[0], "tls.createServer");
2113
+ if (args.length === 1) {
2114
+ return opts.dyn !== undefined
2115
+ ? { kind: "libCall", fn: "tls.createServerDyn", args: [opts.dyn], type: NETSERVER_T, loc }
2116
+ : { kind: "libCall", fn: "tls.createServer", args: [opts.cert, opts.key], type: NETSERVER_T, loc };
2117
+ }
2118
+ const { cb } = lowerCallbackArg(L, args[1], "secureConnection listeners", 1, (p) => p.kind === "netSocket", "use (socket) or ()", [NETSOCKET_T]);
2119
+ return opts.dyn !== undefined
2120
+ ? { kind: "libCall", fn: "tls.createServerDynCb", args: [opts.dyn, cb], type: NETSERVER_T, loc }
2121
+ : { kind: "libCall", fn: "tls.createServerCb", args: [opts.cert, opts.key, cb], type: NETSERVER_T, loc };
2122
+ }
2123
+ if (bi.member === "connect") {
2124
+ return lowerTlsConnectCall(L, expr, loc);
2125
+ }
2126
+ if (bi.member === "createSecureContext") {
2127
+ // createSecureContext({ cert, key }) → the opaque SecureContext handle
2128
+ // an SNI callback answers with. The minimal honest form: exactly the
2129
+ // cert/key pair (PEM strings or Buffers — the createServer options
2130
+ // walk, reused); ca/ciphers/etc. fence by name there.
2131
+ if (args.length !== 1) {
2132
+ L.noLowering(`createSecureContext with ${args.length} arguments`, expr, "the supported form is createSecureContext({ cert, key })");
2133
+ }
2134
+ const { cert, key } = lowerTlsServerOptions(L, args[0], "tls.createSecureContext");
2135
+ return { kind: "libCall", fn: "tls.createSecureContext", args: [cert, key], type: SECURECTX_T, loc };
2136
+ }
2137
+ L.noLowering(`tls.${bi.member}`, expr, builtinFenceHintOf("tls", bi.member) ??
2138
+ "createServer({ cert, key }, handler) and createSecureContext({ cert, key }) are the lowered tls module functions", L.resolveValueSymbol(expr.expression));
2139
+ }
2140
+ const httpClientFnBindings = new WeakMap();
2141
+ export function httpClientFnBindingOf(L, sym) {
2142
+ return httpClientFnBindings.get(L)?.get(sym);
2143
+ }
2144
+ /** The { module, member } of an http/https client-function REFERENCE
2145
+ * (`https.request` through a namespace import, or a named `request`
2146
+ * import binding) — null for anything else. */
2147
+ function clientFnRefOf(L, node) {
2148
+ const e = node;
2149
+ const bi = ts.isPropertyAccessExpression(e)
2150
+ ? L.builtinMemberOf(e)
2151
+ : ts.isIdentifier(e)
2152
+ ? L.builtinImportOf(e)
2153
+ : null;
2154
+ if (!bi)
2155
+ return null;
2156
+ if ((bi.module !== "http" && bi.module !== "https") || (bi.member !== "request" && bi.member !== "get")) {
2157
+ return null;
2158
+ }
2159
+ return { module: bi.module, member: bi.member };
2160
+ }
2161
+ /** True when `sym`'s binding is never written after initialization: a
2162
+ * `const` declaration, or a parameter/let whose enclosing scope contains
2163
+ * no assignment or ++/-- targeting it. */
2164
+ function neverReassigned(L, sym) {
2165
+ const decl = L.checker.declarationsOf(sym)[0];
2166
+ if (!decl)
2167
+ return false;
2168
+ if (ts.isVariableDeclaration(decl) && (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) !== 0) {
2169
+ return true;
2170
+ }
2171
+ if (!ts.isParameter(decl) && !ts.isVariableDeclaration(decl))
2172
+ return false;
2173
+ // The declaring scope: the nearest enclosing function-like body, or the
2174
+ // source file for module-level bindings. Writes anywhere inside
2175
+ // (nested closures included) disqualify.
2176
+ let scope = decl;
2177
+ while (!ts.isFunctionLike(scope.parent) && !ts.isSourceFile(scope.parent))
2178
+ scope = scope.parent;
2179
+ const root = ts.isFunctionLike(scope.parent) ? scope.parent : scope.parent;
2180
+ let written = false;
2181
+ const hitsSym = (n) => {
2182
+ if (ts.isIdentifier(n) && L.checker.getSymbolAtLocation(n) === sym)
2183
+ return true;
2184
+ let hit = false;
2185
+ ts.forEachChild(n, (c) => {
2186
+ if (!hit)
2187
+ hit = hitsSym(c);
2188
+ });
2189
+ return hit;
2190
+ };
2191
+ const visit = (n) => {
2192
+ if (written)
2193
+ return;
2194
+ if (ts.isBinaryExpression(n)) {
2195
+ const k = n.operatorToken.kind;
2196
+ const isAssign = k >= ts.SyntaxKind.FirstAssignment && k <= ts.SyntaxKind.LastAssignment;
2197
+ // The whole LEFT side scans (destructuring targets included).
2198
+ if (isAssign && hitsSym(n.left)) {
2199
+ written = true;
2200
+ return;
2201
+ }
2202
+ }
2203
+ if ((ts.isPrefixUnaryExpression(n) || ts.isPostfixUnaryExpression(n)) &&
2204
+ (n.operator === ts.SyntaxKind.PlusPlusToken || n.operator === ts.SyntaxKind.MinusMinusToken) &&
2205
+ ts.isIdentifier(n.operand) && L.checker.getSymbolAtLocation(n.operand) === sym) {
2206
+ written = true;
2207
+ return;
2208
+ }
2209
+ ts.forEachChild(n, visit);
2210
+ };
2211
+ visit(root);
2212
+ return !written;
2213
+ }
2214
+ /** Recognizes `const x = cond ? <client A> : <client B>` where A and B
2215
+ * are the SAME member (request/get) of https and http (either order) —
2216
+ * registers the binding (calls lower; the declaration itself binds
2217
+ * nothing) and answers true. A matched ternary whose condition is not a
2218
+ * stable identifier fences pointedly; non-client ternaries answer false
2219
+ * (the ordinary decl path and its per-arm fences apply). */
2220
+ export function registerHttpClientFnBinding(L, nameNode, init) {
2221
+ if (!init)
2222
+ return false;
2223
+ let e = init;
2224
+ while (ts.isParenthesizedExpression(e))
2225
+ e = e.expression;
2226
+ if (!ts.isConditionalExpression(e))
2227
+ return false;
2228
+ const unwrap = (a) => {
2229
+ let x = a;
2230
+ while (ts.isParenthesizedExpression(x))
2231
+ x = x.expression;
2232
+ return x;
2233
+ };
2234
+ const t = clientFnRefOf(L, unwrap(e.whenTrue));
2235
+ const f = clientFnRefOf(L, unwrap(e.whenFalse));
2236
+ if (!t || !f)
2237
+ return false;
2238
+ if (t.member !== f.member || t.module === f.module) {
2239
+ L.noLowering("a conditional between these two client functions", e, "the lowered shape is `cond ? https.request : http.request` (or the .get pair, either order) — the same member of both modules");
2240
+ }
2241
+ const cond = unwrap(e.condition);
2242
+ const condSym = ts.isIdentifier(cond) ? L.resolveValueSymbol(cond) : null;
2243
+ if (!condSym || !neverReassigned(L, condSym)) {
2244
+ L.noLowering("a client-function conditional with this condition shape", e.condition, "the condition must be a bare identifier whose binding is never reassigned (it re-evaluates at each call through the binding)");
2245
+ }
2246
+ const symbol = L.checker.getSymbolAtLocation(nameNode);
2247
+ if (symbol) {
2248
+ let map = httpClientFnBindings.get(L);
2249
+ if (!map) {
2250
+ map = new Map();
2251
+ httpClientFnBindings.set(L, map);
2252
+ }
2253
+ map.set(symbol, { cond, trueSecure: t.module === "https", member: t.member });
2254
+ }
2255
+ return true;
2256
+ }
2257
+ /** A call THROUGH a registered requestFn binding: the http client
2258
+ * lowering with the RUNTIME-secure extras (the binding mode). */
2259
+ export function lowerHttpClientFnCall(L, expr, binding, loc) {
2260
+ return lowerHttpClientCall(L, expr, binding.member, loc, binding);
2261
+ }
2262
+ /** Module-function calls on https import bindings: createServer(options,
2263
+ * handler) and request/get — the http client lowering with the secure
2264
+ * extras (port 443 default, rejectUnauthorized, ca). */
2265
+ function lowerHttpsModuleCall(L, expr, bi, loc) {
2266
+ const args = expr.arguments;
2267
+ if (bi.member === "createServer") {
2268
+ if (args.length < 1 || args.length > 2) {
2269
+ L.noLowering(`createServer with ${args.length} arguments`, expr, "the supported form is createServer({ cert, key }, (req, res) => ...)");
2270
+ }
2271
+ const opts = lowerTlsServerOptionsOrDyn(L, args[0], "https.createServer");
2272
+ if (args.length === 1) {
2273
+ // The handler-less form: 'request' listeners arrive later via
2274
+ // server.on("request", ...) — the createSecureServer emission's
2275
+ // NULL-handler shape, here for the runtime-options record only
2276
+ // (the literal path keeps its historical two-argument surface).
2277
+ if (opts.dyn !== undefined) {
2278
+ return { kind: "libCall", fn: "https.createServerDyn", args: [opts.dyn], type: NETSERVER_T, loc };
2279
+ }
2280
+ L.noLowering("createServer with 1 arguments", expr, "the supported form is createServer({ cert, key }, (req, res) => ...)");
2281
+ }
2282
+ const cb = lowerRequestHandlerArg(L, args[1]);
2283
+ return opts.dyn !== undefined
2284
+ ? { kind: "libCall", fn: "https.createServerDynCb", args: [opts.dyn, cb], type: NETSERVER_T, loc }
2285
+ : { kind: "libCall", fn: "https.createServer", args: [opts.cert, opts.key, cb], type: NETSERVER_T, loc };
2286
+ }
2287
+ if (bi.member === "request" || bi.member === "get") {
2288
+ return lowerHttpClientCall(L, expr, bi.member, loc, true);
2289
+ }
2290
+ L.noLowering(`https.${bi.member}`, expr, "createServer, request, and get are the lowered https module functions", L.resolveValueSymbol(expr.expression));
2291
+ }
2292
+ /** The h2 SESSION-tuning options of http2.createSecureServer: knobs that
2293
+ * configure HTTP/2 sessions, which the allowHTTP1 lowering never creates
2294
+ * (every connection serves HTTP/1.1 — SEMANTICS.md divergence 57). They
2295
+ * are accepted and IGNORED — Node before 22.11 ignores the streamReset
2296
+ * pair the same way — but only with LITERAL values, so nothing observable
2297
+ * (a call, a read) is silently skipped. */
2298
+ const HTTP2_IGNORED_TUNING_OPTIONS = new Set([
2299
+ "streamResetBurst",
2300
+ "streamResetRate",
2301
+ "maxSessionMemory",
2302
+ "maxDeflateDynamicTableSize",
2303
+ "maxSettings",
2304
+ "maxHeaderListPairs",
2305
+ "maxOutstandingPings",
2306
+ "maxSendHeaderBlockLength",
2307
+ "paddingStrategy",
2308
+ "peerMaxConcurrentStreams",
2309
+ "settings",
2310
+ ]);
2311
+ const SNI_HINT = "SNICallback lowers as a direct option or the conditional spread " +
2312
+ "...(x ? { SNICallback: x } : {}) — the callback is (servername, cb) => void " +
2313
+ "with cb: (err: Error | null, ctx?: tls.SecureContext) => void";
2314
+ /** Strips parentheses and type assertions: the portless spelling wraps
2315
+ * an options spread as `...({ ... } as Record<string, unknown>)`. */
2316
+ function stripParensAndCasts(node) {
2317
+ let e = node;
2318
+ for (;;) {
2319
+ if (ts.isParenthesizedExpression(e))
2320
+ e = e.expression;
2321
+ else if (ts.isAsExpression(e) || ts.isTypeAssertion(e))
2322
+ e = e.expression;
2323
+ else
2324
+ return e;
2325
+ }
2326
+ }
2327
+ /** Module-function calls on http2 import bindings. The ONE lowered member
2328
+ * is createSecureServer with allowHTTP1: true — the honest HTTP/1.1
2329
+ * fallback: the existing TLS server + HTTP/1.1 parser, ALPN advertising
2330
+ * http/1.1 only (h2-capable clients negotiate down; h2-only clients fail
2331
+ * the handshake — SEMANTICS.md divergence 57). The handler arrives later
2332
+ * via server.on("request", ...); h2 sessions (connect, the h2c
2333
+ * createServer) and h2-only servers keep their fences. */
2334
+ function lowerHttp2ModuleCall(L, expr, bi, loc) {
2335
+ const args = expr.arguments;
2336
+ if (bi.member === "createServer") {
2337
+ // The REAL h2c server (scr_http2.c). An options object literal is
2338
+ // accepted and ignored (h2 session-tuning knobs; the honest-defaults
2339
+ // stance) — a NON-literal options argument or an eager (req, res)
2340
+ // handler is NOT the lowered shape (register via server.on("stream")).
2341
+ let handlerNode = null;
2342
+ for (const a of args) {
2343
+ if (ts.isObjectLiteralExpression(a))
2344
+ continue; // tuning options: ignored
2345
+ if (ts.isFunctionExpression(a) || ts.isArrowFunction(a)) {
2346
+ handlerNode = a;
2347
+ continue;
2348
+ }
2349
+ // A function-typed OR dyn argument is the eager compat handler
2350
+ // (`createServer(common.mustCall(handler))` — the suite's shape);
2351
+ // anything else is a tuning-options VALUE — accepted and never
2352
+ // evaluated (h2 knobs this core runs at defaults; the options
2353
+ // expression is a pure read in every suite shape).
2354
+ const t = L.typeOf(a);
2355
+ if (L.checker.getCallSignatures(t).length > 0 ||
2356
+ (t.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {
2357
+ handlerNode = a;
2358
+ }
2359
+ }
2360
+ if (handlerNode === null) {
2361
+ return { kind: "libCall", fn: "http2.createServer", args: [], type: NETSERVER_T, loc };
2362
+ }
2363
+ // An eager (req, res) handler is the COMPAT surface: the handler
2364
+ // becomes the first 'request' listener (Node's exact route), and the
2365
+ // pair it receives ARE the http req/res handles — Http2ServerRequest/
2366
+ // Http2ServerResponse over h2 streams (scr_http2.c's compat layer).
2367
+ const cb = lowerRequestHandlerArg(L, handlerNode);
2368
+ return { kind: "libCall", fn: "http2.createServerReq", args: [cb], type: NETSERVER_T, loc };
2369
+ }
2370
+ if (bi.member === "connect") {
2371
+ // http2.connect(authority[, listener]) — the h2c client. The
2372
+ // authority is a string (URL objects and an options record fence).
2373
+ if (args.length < 1 || args.length > 3) {
2374
+ L.noLowering(`http2.connect with ${args.length} arguments`, expr, "the supported form is connect(authority[, options][, listener]) with a string authority");
2375
+ }
2376
+ const auth = L.lowerExpr(args[0]);
2377
+ if (auth.type.kind !== "string") {
2378
+ L.noLowering(`http2.connect with a '${L.fmt(auth.type)}' authority`, args[0], "the authority is a string here (\"http://host:port\")");
2379
+ }
2380
+ // connect(authority, options[, listener]): session-tuning options
2381
+ // are accepted and never evaluated (the core runs at defaults) —
2382
+ // EXCEPT createConnection (a transport change: fence) and the TLS
2383
+ // client knobs an https authority reads: rejectUnauthorized (a
2384
+ // literal bool) and ca (a PEM string/Buffer — runtime values ride
2385
+ // the pemDyn extraction). On an http authority both are inert,
2386
+ // exactly Node. A DYN second argument is the LISTENER
2387
+ // (`connect(url, mustCall(cb))`, the suite's shape — options arrive
2388
+ // as object literals); the dyn flavor adapts through the checked
2389
+ // function boundary below.
2390
+ let listenerNode = args[1];
2391
+ let reject = { kind: "boolLit", value: true, type: BOOL, loc };
2392
+ let ca = { kind: "strLit", value: "", type: STRING, loc };
2393
+ const arg1IsDyn = args.length >= 2 &&
2394
+ (L.typeOf(args[1]).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0;
2395
+ if (args.length >= 2 && !arg1IsDyn &&
2396
+ L.checker.getCallSignatures(L.typeOf(args[1])).length === 0) {
2397
+ if (ts.isObjectLiteralExpression(args[1])) {
2398
+ for (const pr of args[1].properties) {
2399
+ // Plain `name: value` and shorthand `{ ca }` both carry the
2400
+ // option; a shorthand's value is the same-named identifier.
2401
+ let name;
2402
+ let valueNode;
2403
+ if (ts.isPropertyAssignment(pr) && ts.isIdentifier(pr.name)) {
2404
+ name = pr.name.text;
2405
+ valueNode = pr.initializer;
2406
+ }
2407
+ else if (ts.isShorthandPropertyAssignment(pr)) {
2408
+ name = pr.name.text;
2409
+ valueNode = pr.name;
2410
+ }
2411
+ else {
2412
+ continue;
2413
+ }
2414
+ if (name === "createConnection") {
2415
+ L.noLowering("http2.connect with a createConnection option", args[1], "custom transports are not modeled — connect dials TCP itself");
2416
+ }
2417
+ if (name === "rejectUnauthorized") {
2418
+ if (valueNode.kind === ts.SyntaxKind.FalseKeyword) {
2419
+ reject = { kind: "boolLit", value: false, type: BOOL, loc };
2420
+ }
2421
+ else if (valueNode.kind !== ts.SyntaxKind.TrueKeyword) {
2422
+ L.noLowering("http2.connect with a non-literal rejectUnauthorized option", pr, "spell it true or false — it gates the https authority's certificate verification");
2423
+ }
2424
+ }
2425
+ if (name === "ca") {
2426
+ const v = L.lowerExpr(valueNode);
2427
+ if (v.type.kind === "string" || (v.type.kind === "bytes" && v.type.elem === "u8")) {
2428
+ ca = v;
2429
+ }
2430
+ else if (v.type.kind === "dyn") {
2431
+ ca = {
2432
+ kind: "libCall",
2433
+ fn: "tls.pemDyn",
2434
+ args: [v, { kind: "strLit", value: "an http2.connect 'ca' option", type: STRING, loc }],
2435
+ type: BYTES_U8,
2436
+ loc,
2437
+ };
2438
+ }
2439
+ else {
2440
+ L.noLowering(`an http2.connect 'ca' option of '${L.fmt(v.type)}' values`, pr, "ca is a PEM string or Buffer here");
2441
+ }
2442
+ }
2443
+ }
2444
+ }
2445
+ listenerNode = args[2];
2446
+ }
2447
+ else if (args.length === 3) {
2448
+ listenerNode = args[2];
2449
+ }
2450
+ if (listenerNode !== undefined) {
2451
+ // The listener is the 'connect' once-listener: (session, socket) or
2452
+ // fewer. Its result is ignored (voidized); handles pass by ref.
2453
+ const { cb } = lowerCallbackArg(L, listenerNode, "connect listeners", 2, (p) => p.kind === "http2Session" || p.kind === "netSocket", "use (session, socket), (session), or ()", [HTTP2SESSION_T, NETSOCKET_T]);
2454
+ return { kind: "libCall", fn: "http2.connectCb", args: [auth, reject, ca, cb], type: HTTP2SESSION_T, loc };
2455
+ }
2456
+ return { kind: "libCall", fn: "http2.connect", args: [auth, reject, ca], type: HTTP2SESSION_T, loc };
2457
+ }
2458
+ if (bi.member === "getDefaultSettings") {
2459
+ // The constant defaults record, as a DOM value (the d.ts types it
2460
+ // `any` — reads ride the checked-dynamic keyed read).
2461
+ return { kind: "libCall", fn: "http2.getDefaultSettings", args: [], type: DYN, loc };
2462
+ }
2463
+ if (bi.member === "createSecureServer") {
2464
+ if (args.length !== 1) {
2465
+ L.noLowering(`createSecureServer with ${args.length} arguments`, expr, "the supported form is createSecureServer({ allowHTTP1: true, cert, key }) — register the handler with server.on(\"request\", (req, res) => ...)");
2466
+ }
2467
+ const optsNode = args[0];
2468
+ if (!ts.isObjectLiteralExpression(optsNode)) {
2469
+ L.noLowering("createSecureServer with a non-literal options argument", optsNode, "pass the options as an object literal: { allowHTTP1: true, cert, key }");
2470
+ }
2471
+ let cert = null;
2472
+ let key = null;
2473
+ let sni = null;
2474
+ let allowHttp1 = false;
2475
+ // The SNICallback value shape: (servername: string, cb) => void where
2476
+ // cb is (err: Error | null, ctx?: SecureContext) => void — cb may
2477
+ // declare fewer params (the emitted answer thunk matches its ABI),
2478
+ // but what it does declare must carry the arms the thunk decodes.
2479
+ const sniShapeOk = (t) => {
2480
+ if (t.kind !== "func" || t.ret.kind !== "void" || t.params.length !== 2)
2481
+ return false;
2482
+ if (t.params[0].kind !== "string")
2483
+ return false;
2484
+ const cbT = t.params[1];
2485
+ if (cbT.kind !== "func" || cbT.ret.kind !== "void" || cbT.params.length > 2)
2486
+ return false;
2487
+ const armKinds = (u) => u.kind === "union" ? (L.unions.get(u.unionId)?.arms ?? []).map((a) => a.kind) : [];
2488
+ const p0 = cbT.params[0];
2489
+ if (p0 !== undefined && !armKinds(p0).includes("nullT"))
2490
+ return false;
2491
+ const p1 = cbT.params[1];
2492
+ if (p1 !== undefined && !armKinds(p1).includes("secureCtx"))
2493
+ return false;
2494
+ return true;
2495
+ };
2496
+ const lowerSniValue = (node, blame) => {
2497
+ const v = L.lowerExpr(node);
2498
+ const funcArm = v.type.kind === "func"
2499
+ ? v.type
2500
+ : v.type.kind === "union"
2501
+ ? (L.unions.get(v.type.unionId)?.arms ?? []).find((a) => a.kind === "func")
2502
+ : undefined;
2503
+ const unionOk = v.type.kind !== "union" ||
2504
+ ((L.unions.get(v.type.unionId)?.arms ?? []).length === 2 &&
2505
+ (L.unions.get(v.type.unionId)?.arms ?? []).some((a) => a.kind === "undefinedT"));
2506
+ if (funcArm === undefined || !unionOk || !sniShapeOk(funcArm)) {
2507
+ L.noLowering(`a createSecureServer SNICallback of '${L.fmt(v.type)}' values`, blame, SNI_HINT);
2508
+ }
2509
+ sni = v;
2510
+ };
2511
+ const lowerOption = (prop) => {
2512
+ if (ts.isSpreadAssignment(prop)) {
2513
+ // Spreads of INLINE object literals (parens/as-casts stripped —
2514
+ // the `...({ streamResetBurst: 10000 } as Record<string,
2515
+ // unknown>)` spelling) flatten into the same option walk. The
2516
+ // CONDITIONAL spread `...(x ? { SNICallback: x } : {})` — the
2517
+ // portless proxy shape — lowers as the SNICallback | undefined
2518
+ // union: the spread includes the option exactly when x is truthy,
2519
+ // and a func | undefined union is truthy exactly on its func arm,
2520
+ // so passing the union (undefined arm = no callback) is the same
2521
+ // semantics — PROVIDED the included value IS the condition, which
2522
+ // the text check pins. Any other spread operand is opaque to the
2523
+ // static walk.
2524
+ const inner = stripParensAndCasts(prop.expression);
2525
+ if (ts.isConditionalExpression(inner)) {
2526
+ const whenTrue = stripParensAndCasts(inner.whenTrue);
2527
+ const whenFalse = stripParensAndCasts(inner.whenFalse);
2528
+ const condText = inner.condition.getText();
2529
+ const ok = ts.isObjectLiteralExpression(whenTrue) &&
2530
+ ts.isObjectLiteralExpression(whenFalse) &&
2531
+ whenFalse.properties.length === 0 &&
2532
+ whenTrue.properties.length === 1 &&
2533
+ ts.isPropertyAssignment(whenTrue.properties[0]) &&
2534
+ (ts.isIdentifier(whenTrue.properties[0].name) ||
2535
+ ts.isStringLiteral(whenTrue.properties[0].name)) &&
2536
+ whenTrue.properties[0].name.text === "SNICallback" &&
2537
+ whenTrue.properties[0].initializer.getText() === condText;
2538
+ if (!ok) {
2539
+ L.noLowering("createSecureServer options with a conditional spread", prop, "the lowered conditional spread is exactly ...(x ? { SNICallback: x } : {}) — the included value must BE the condition");
2540
+ }
2541
+ lowerSniValue(inner.condition, prop);
2542
+ return;
2543
+ }
2544
+ if (!ts.isObjectLiteralExpression(inner)) {
2545
+ L.noLowering("createSecureServer options with a computed spread", prop, `spreads must be inline object literals here; ${SNI_HINT}`);
2546
+ }
2547
+ for (const p of inner.properties)
2548
+ lowerOption(p);
2549
+ return;
2550
+ }
2551
+ let initializer;
2552
+ if (ts.isPropertyAssignment(prop) &&
2553
+ (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) {
2554
+ initializer = prop.initializer;
2555
+ }
2556
+ else if (ts.isShorthandPropertyAssignment(prop)) {
2557
+ initializer = null;
2558
+ }
2559
+ else {
2560
+ L.noLowering("createSecureServer options with computed keys", prop, "each option must be a plain `name: value` (or shorthand) entry with a literal key");
2561
+ }
2562
+ const k = prop.name.text;
2563
+ if (k === "allowHTTP1") {
2564
+ // A literal true or false: true keeps the compatibility server
2565
+ // (HTTP/1.1 only — divergence 57's remaining arm); false IS the
2566
+ // absent default — the h2-only ALPN server below. A runtime
2567
+ // value would pick a protocol stack dynamically — fence.
2568
+ if (initializer !== null && initializer.kind === ts.SyntaxKind.FalseKeyword)
2569
+ return;
2570
+ if (initializer === null || initializer.kind !== ts.SyntaxKind.TrueKeyword) {
2571
+ L.noLowering("createSecureServer with a non-literal allowHTTP1 option", prop, "allowHTTP1 picks the protocol stack (true: the HTTP/1.1 compatibility server; false/absent: the ALPN=h2 server) — spell it as a literal");
2572
+ }
2573
+ allowHttp1 = true;
2574
+ return;
2575
+ }
2576
+ if (k === "cert" || k === "key") {
2577
+ let v = initializer !== null
2578
+ ? L.lowerExpr(initializer)
2579
+ : L.lowerShorthandValue(prop);
2580
+ if (v.type.kind === "dyn") {
2581
+ // A runtime PEM value (fixtures.readKey(...)): the tls walk's
2582
+ // runtime extraction, same fences at runtime.
2583
+ v = {
2584
+ kind: "libCall",
2585
+ fn: "tls.pemDyn",
2586
+ args: [v, { kind: "strLit", value: `a createSecureServer '${k}' option`, type: STRING, loc }],
2587
+ type: BYTES_U8,
2588
+ loc,
2589
+ };
2590
+ }
2591
+ else if (v.type.kind !== "string" && !(v.type.kind === "bytes" && v.type.elem === "u8")) {
2592
+ L.noLowering(`a createSecureServer '${k}' option of '${L.fmt(v.type)}' values`, prop, "cert/key are PEM strings or Buffers here");
2593
+ }
2594
+ if (k === "cert")
2595
+ cert = v;
2596
+ else
2597
+ key = v;
2598
+ return;
2599
+ }
2600
+ if (HTTP2_IGNORED_TUNING_OPTIONS.has(k)) {
2601
+ // Ignored, literals only (see the set's comment). `settings` takes
2602
+ // a NESTED literal of literals (`settings: { enableConnectProtocol:
2603
+ // true }` — the portless RFC 8441 advertisement): every member is
2604
+ // itself inert h2-session state, so the same no-observable-skip
2605
+ // argument covers the one level of nesting.
2606
+ const isScalarLiteral = (e) => ts.isNumericLiteral(e) || ts.isStringLiteralLike(e) ||
2607
+ e.kind === ts.SyntaxKind.TrueKeyword || e.kind === ts.SyntaxKind.FalseKeyword;
2608
+ const isLiteralObjectOfLiterals = (e) => ts.isObjectLiteralExpression(e) &&
2609
+ e.properties.every((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && isScalarLiteral(p.initializer));
2610
+ if (initializer !== null &&
2611
+ (isScalarLiteral(initializer) || isLiteralObjectOfLiterals(initializer))) {
2612
+ return;
2613
+ }
2614
+ L.noLowering(`createSecureServer option '${k}' with a non-literal value`, prop, "h2 session-tuning options are accepted with literal values (settings: a literal of literals) and ignored — the allowHTTP1 lowering serves HTTP/1.1 only, which has no h2 sessions to tune");
2615
+ }
2616
+ if (k === "SNICallback") {
2617
+ if (initializer === null) {
2618
+ L.noLowering("createSecureServer with a shorthand SNICallback option", prop, "spell it out: SNICallback: theCallback");
2619
+ }
2620
+ lowerSniValue(initializer, prop);
2621
+ return;
2622
+ }
2623
+ fenceOrDropOptionKey(L, prop, k, "createSecureServer", HTTP2_SECURE_SERVER_DOCUMENTED_OPTIONS, "allowHTTP1: true, cert, key, and SNICallback are the supported options (h2 session-tuning options are accepted as literals and ignored)");
2624
+ // An undocumented key, dropped like Node drops it.
2625
+ };
2626
+ for (const prop of optsNode.properties)
2627
+ lowerOption(prop);
2628
+ if (cert === null || key === null) {
2629
+ L.noLowering("createSecureServer without both cert and key", optsNode, "the supported options object is { allowHTTP1?, cert, key } (PEM strings or Buffers)");
2630
+ }
2631
+ if (!allowHttp1) {
2632
+ // The REAL h2-over-TLS server (scr_http2.c + scr_tls.c): ALPN
2633
+ // advertises h2 alone and the h2 session attaches at establishment
2634
+ // — 'stream'/'session' listeners, exactly the h2c server's surface.
2635
+ // SNI callbacks are an https-path feature; the h2 stack serves one
2636
+ // cert/key pair.
2637
+ if (sni !== null) {
2638
+ L.noLowering("createSecureServer with an SNICallback and no allowHTTP1", optsNode, "SNICallback lowers on the allowHTTP1 compatibility server — the ALPN=h2 server serves one cert/key pair");
2639
+ }
2640
+ return { kind: "libCall", fn: "http2.createSecureServerH2", args: [cert, key], type: NETSERVER_T, loc };
2641
+ }
2642
+ if (sni !== null) {
2643
+ return { kind: "libCall", fn: "http2.createSecureServerSni", args: [cert, key, sni], type: NETSERVER_T, loc };
2644
+ }
2645
+ return { kind: "libCall", fn: "http2.createSecureServer", args: [cert, key], type: NETSERVER_T, loc };
2646
+ }
2647
+ L.noLowering(`http2.${bi.member}`, expr, builtinFenceHintOf("http2", bi.member) ??
2648
+ "createSecureServer({ allowHTTP1: true, cert, key }) is the lowered http2 surface — it serves HTTP/1.1 only (ALPN never advertises h2); h2 sessions have no lowering", L.resolveValueSymbol(expr.expression));
2649
+ }
2650
+ /** http.request(options[, cb]) / http.get(options[, cb]) — the client.
2651
+ * The options argument must be an OBJECT LITERAL; the lowered keys are
2652
+ * hostname/host, port, path, method, timeout, and headers (a nested
2653
+ * object literal with string values, or any Record whose values are
2654
+ * strings / `string | undefined` — the env.pairs shape). Every other key
2655
+ * fences by name. The callback registers as once('response'); get() is
2656
+ * request() plus the eager end(). The url-string first argument has no
2657
+ * lowering (build the options object instead). `secure` is the https
2658
+ * spelling: port defaults to 443 and the rejectUnauthorized (boolean)
2659
+ * and ca (PEM string/Buffer) options join the lowered set. A
2660
+ * HttpClientFnBinding as `secure` is the requestFn-binding mode: whether
2661
+ * the dial is TLS is a RUNTIME bool (the binding's condition, re-lowered
2662
+ * per use — a pure identifier read), the port default follows it (443 /
2663
+ * 80), the https extras stay lowered (the plain arm ignores them at
2664
+ * runtime, exactly Node's http.request with TLS options), and the one
2665
+ * supported options SPREAD is the conditional `...(c ? {
2666
+ * rejectUnauthorized: <bool> } : {})` (either orientation) — the
2667
+ * portless isProxyRunning shape. */
2668
+ function lowerHttpClientCall(L, expr, member, loc, secure = false) {
2669
+ const binding = typeof secure === "object" ? secure : null;
2670
+ const secureish = binding !== null || secure === true;
2671
+ /** A FRESH lowering of the binding's secure condition (pure identifier
2672
+ * read — each use is its own IR). */
2673
+ const secureExpr = () => {
2674
+ const c = L.lowerCondition(binding.cond);
2675
+ return binding.trueSecure
2676
+ ? c
2677
+ : { kind: "unary", op: "!", operand: c, type: BOOL, loc };
2678
+ };
2679
+ const args = expr.arguments;
2680
+ if (args.length < 1 || args.length > 2) {
2681
+ L.noLowering(`${member} with ${args.length} arguments`, expr, `the supported form is ${member}(options[, callback])`);
2682
+ }
2683
+ const optsNode = args[0];
2684
+ if (!ts.isObjectLiteralExpression(optsNode)) {
2685
+ const t = L.mapTypeOf(L.typeOf(optsNode));
2686
+ if (t?.kind === "string" && !secureish) {
2687
+ // The URL-string form — http.get(`http://127.0.0.1:${port}/x`[, cb]):
2688
+ // the runtime parses through the WHATWG unit and dials; unparsable
2689
+ // inputs and non-http schemes throw catchably (may-throw seed).
2690
+ const url = L.lowerExprExpecting(optsNode, STRING);
2691
+ const methodLit = { kind: "strLit", value: "GET", type: STRING, loc };
2692
+ const autoEnd = boolLit(member === "get", loc);
2693
+ if (args.length === 1) {
2694
+ return { kind: "libCall", fn: "http.requestUrl", args: [url, methodLit, autoEnd], type: HTTPCLIENTREQ_T, loc };
2695
+ }
2696
+ const { cb } = lowerCallbackArg(L, args[1], "response callbacks", 1, (p) => p.kind === "httpReq", "use (res) or ()", [HTTPREQ_T]);
2697
+ return { kind: "libCall", fn: "http.requestUrlCb", args: [url, methodLit, autoEnd, cb], type: HTTPCLIENTREQ_T, loc };
2698
+ }
2699
+ L.noLowering(t?.kind === "string"
2700
+ ? `${member} with a URL-string first argument`
2701
+ : `${member} with a non-literal options argument`, optsNode, "pass the options as an object literal: { hostname, port, path, method, timeout?, headers? }");
2702
+ }
2703
+ let host = null;
2704
+ let port = null;
2705
+ let path = null;
2706
+ let method = null;
2707
+ let timeout = null;
2708
+ let headers = null;
2709
+ let reject = null;
2710
+ let ca = null;
2711
+ let connCb = null;
2712
+ /** agent: false — inject Connection: close into the request head. */
2713
+ let agentClose = null;
2714
+ for (const prop of optsNode.properties) {
2715
+ // Shorthand entries ({ port } for { port: port }) are the natural
2716
+ // spelling at these call sites — the shorthand's VALUE lowering
2717
+ // resolves the local binding (lowerShorthandValue), not the property
2718
+ // symbol.
2719
+ // The conditional-spread idiom `...(c ? { rejectUnauthorized: <bool>
2720
+ // } : {})` (either orientation) — the portless isProxyRunning shape:
2721
+ // the option applies exactly when c holds, and Node's default (true,
2722
+ // verify) is what the empty arm leaves in place, so the value lowers
2723
+ // as one ternary over c. Only the https-capable modes accept it (the
2724
+ // plain client has no TLS layer to configure).
2725
+ if (ts.isSpreadAssignment(prop)) {
2726
+ const cs = conditionalSpreadOf(prop.expression);
2727
+ if (cs !== null && cs !== "unsupported" && cs.props.length === 1 &&
2728
+ cs.props[0].name.text === "rejectUnauthorized" &&
2729
+ ts.isPropertyAssignment(cs.props[0]) && secureish) {
2730
+ const cond = L.lowerCondition(cs.cond);
2731
+ const v = L.lowerExpr(cs.props[0].initializer);
2732
+ if (v.type.kind !== "bool") {
2733
+ L.noLowering(`a ${member} 'rejectUnauthorized' option of '${L.fmt(v.type)}' values`, cs.props[0], "the option value must be a boolean");
2734
+ }
2735
+ const dflt = boolLit(true, loc); /* Node's default: verify */
2736
+ reject = {
2737
+ kind: "ternary",
2738
+ cond,
2739
+ then: cs.whenTrue ? v : dflt,
2740
+ else_: cs.whenTrue ? dflt : v,
2741
+ type: BOOL,
2742
+ loc,
2743
+ };
2744
+ continue;
2745
+ }
2746
+ L.noLowering(`${member} with this options spread`, prop, secureish
2747
+ ? "the one supported spread is the conditional `...(c ? { rejectUnauthorized: <bool> } : {})` (either orientation) — write other members inline"
2748
+ : "spreads have no lowering here — write each option inline");
2749
+ }
2750
+ let initializer;
2751
+ if (ts.isPropertyAssignment(prop) &&
2752
+ (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) {
2753
+ initializer = prop.initializer;
2754
+ }
2755
+ else if (ts.isShorthandPropertyAssignment(prop)) {
2756
+ initializer = null;
2757
+ }
2758
+ else {
2759
+ L.noLowering(`${member} options with computed keys`, prop, "each option must be a plain `name: value` (or shorthand) entry with a literal key");
2760
+ }
2761
+ const key = prop.name.text;
2762
+ const lowerVal = (want) => {
2763
+ const v = initializer !== null
2764
+ ? L.lowerExpr(initializer)
2765
+ : L.lowerShorthandValue(prop);
2766
+ // A checked-dynamic value (an untyped JS binding carrying the
2767
+ // port/path through a helper): dynCheck validates it into the
2768
+ // option's type — a mismatch is the catchable path-annotated
2769
+ // TypeError, the boundary's stance.
2770
+ if (v.type.kind === "dyn") {
2771
+ return { kind: "dynCheck", value: v, type: want === "string" ? STRING : F64, loc: locOf(prop) };
2772
+ }
2773
+ if (v.type.kind !== want) {
2774
+ L.noLowering(`a ${member} '${key}' option of '${L.fmt(v.type)}' values`, prop, want === "string" ? "the option value must be a string" : "the option value must be a number");
2775
+ }
2776
+ return v;
2777
+ };
2778
+ switch (key) {
2779
+ case "hostname":
2780
+ case "host":
2781
+ host = lowerVal("string");
2782
+ break;
2783
+ case "port":
2784
+ port = lowerVal("f64");
2785
+ break;
2786
+ case "path":
2787
+ path = lowerVal("string");
2788
+ break;
2789
+ case "method":
2790
+ method = lowerVal("string");
2791
+ break;
2792
+ case "timeout":
2793
+ timeout = lowerVal("f64");
2794
+ break;
2795
+ case "headers":
2796
+ if (initializer === null) {
2797
+ L.noLowering(`${member} with a shorthand headers option`, prop, "spell it out: headers: theHeaders");
2798
+ }
2799
+ headers = lowerClientHeadersOption(L, initializer);
2800
+ break;
2801
+ case "createConnection": {
2802
+ // The caller's own dialer: a `() => net.Socket` closure the
2803
+ // runtime invokes once, synchronously (the proxy's loopback
2804
+ // dial). https keeps the fence — the TLS client owns its socket —
2805
+ // and the binding mode inherits it (its https arm would).
2806
+ if (secureish) {
2807
+ L.noLowering(`${member} with a createConnection option`, prop, "the https client dials its own TLS socket — pass hostname/port");
2808
+ }
2809
+ if (initializer === null) {
2810
+ L.noLowering(`${member} with a shorthand createConnection option`, prop, "spell it out: createConnection: theDialer");
2811
+ }
2812
+ const v = L.lowerExpr(initializer);
2813
+ if (v.type.kind !== "func" || v.type.params.length !== 0 || v.type.ret.kind !== "netSocket") {
2814
+ L.noLowering(`a ${member} 'createConnection' option of '${L.fmt(v.type)}' values`, prop, "the dialer is () => net.Socket — no arguments, returning the socket to use");
2815
+ }
2816
+ connCb = v;
2817
+ break;
2818
+ }
2819
+ case "rejectUnauthorized": {
2820
+ if (!secureish) {
2821
+ L.noLowering(`${member} option 'rejectUnauthorized'`, prop, "rejectUnauthorized is an https.request option — the plain http client has no TLS layer");
2822
+ }
2823
+ const v = initializer !== null
2824
+ ? L.lowerExpr(initializer)
2825
+ : L.lowerShorthandValue(prop);
2826
+ if (v.type.kind !== "bool") {
2827
+ L.noLowering(`a ${member} 'rejectUnauthorized' option of '${L.fmt(v.type)}' values`, prop, "the option value must be a boolean");
2828
+ }
2829
+ reject = v;
2830
+ break;
2831
+ }
2832
+ case "ca": {
2833
+ if (!secureish) {
2834
+ L.noLowering(`${member} option 'ca'`, prop, "ca is an https.request option — the plain http client has no TLS layer");
2835
+ }
2836
+ const v = initializer !== null
2837
+ ? L.lowerExpr(initializer)
2838
+ : L.lowerShorthandValue(prop);
2839
+ if (v.type.kind !== "string" && !(v.type.kind === "bytes" && v.type.elem === "u8")) {
2840
+ L.noLowering(`a ${member} 'ca' option of '${L.fmt(v.type)}' values`, prop, "the ca is a PEM string or Buffer here");
2841
+ }
2842
+ ca = v;
2843
+ break;
2844
+ }
2845
+ case "agent": {
2846
+ // Node's agent option. `false` asks for a one-shot dial with
2847
+ // Connection: close — exactly the compiled client's connection
2848
+ // model, so it LOWERS (the header injects below). null/undefined
2849
+ // pick the default agent, whose keep-alive request header the
2850
+ // compiled client already sends (socket REUSE stays the
2851
+ // documented divergence either way). Agent instances configure
2852
+ // pooling the runtime does not have — fence.
2853
+ const e = initializer !== null ? stripParensAndCasts(initializer) : null;
2854
+ if (e !== null && e.kind === ts.SyntaxKind.FalseKeyword) {
2855
+ agentClose = prop;
2856
+ break;
2857
+ }
2858
+ if (e !== null &&
2859
+ (e.kind === ts.SyntaxKind.NullKeyword ||
2860
+ (ts.isIdentifier(e) && e.text === "undefined"))) {
2861
+ break; // the default agent: what the agent-free call compiles
2862
+ }
2863
+ L.noLowering(`a ${member} 'agent' option carrying an Agent value`, prop, "compiled clients dial one connection per request and close it with the response — " +
2864
+ "agent: false (lowered: sends Connection: close) and agent: null/undefined (the default) " +
2865
+ "compile; pooling Agent instances have no lowering");
2866
+ break;
2867
+ }
2868
+ default:
2869
+ fenceOrDropOptionKey(L, prop, key, member, secureish ? HTTPS_CLIENT_DOCUMENTED_OPTIONS : HTTP_CLIENT_DOCUMENTED_OPTIONS, secureish
2870
+ ? "hostname/host, port, path, method, timeout, headers, agent, rejectUnauthorized, and ca are the supported options"
2871
+ : "hostname/host, port, path, method, timeout, headers, and agent are the supported options");
2872
+ }
2873
+ }
2874
+ if (agentClose !== null) {
2875
+ // agent: false → Node sends `Connection: close` (its one-shot Agent
2876
+ // sets the header) and tears the socket down with the response — the
2877
+ // compiled client already closes, so the header is the whole
2878
+ // lowering. A user-set connection header wins, Node's rule; a
2879
+ // non-literal headers record cannot be checked for one, so the combo
2880
+ // fences instead of double-sending.
2881
+ const strLitAt = (value) => ({ kind: "strLit", value, type: STRING, loc });
2882
+ if (headers === null) {
2883
+ headers = { kind: "arrayLit", elems: [strLitAt("Connection"), strLitAt("close")], type: arrayOf(STRING), loc };
2884
+ }
2885
+ else if (headers.kind === "arrayLit") {
2886
+ const hasConnection = headers.elems.some((el, i) => i % 2 === 0 && el.kind === "strLit" && el.value.toLowerCase() === "connection");
2887
+ if (!hasConnection) {
2888
+ headers = { ...headers, elems: [...headers.elems, strLitAt("Connection"), strLitAt("close")] };
2889
+ }
2890
+ }
2891
+ else {
2892
+ L.noLowering(`${member} with agent: false and a non-literal headers record`, agentClose, "agent: false lowers by injecting Connection: close into a LITERAL headers object — " +
2893
+ "add connection: \"close\" to the record instead, or write the headers inline");
2894
+ }
2895
+ }
2896
+ const strLit = (value) => ({ kind: "strLit", value, type: STRING, loc });
2897
+ const numLit = (value) => ({ kind: "numLit", value, type: F64, loc });
2898
+ if (connCb !== null && (host !== null || port !== null)) {
2899
+ // Node would use host/port only for the Host header once a dialer
2900
+ // exists — a silent half-use; set headers.host instead.
2901
+ L.noLowering(`${member} mixing createConnection with hostname/host/port`, optsNode, "the dialer supplies the socket — set the Host header via headers: { host: ... } instead");
2902
+ }
2903
+ host ??= strLit("localhost");
2904
+ // The binding mode's default port follows the runtime dial: 443 on the
2905
+ // TLS arm, 80 on the plain one — exactly each client's own default.
2906
+ port ??= binding !== null
2907
+ ? { kind: "ternary", cond: secureExpr(), then: numLit(443), else_: numLit(80), type: F64, loc }
2908
+ : numLit(secure === true ? 443 : 80);
2909
+ path ??= strLit("/");
2910
+ method ??= strLit("GET");
2911
+ timeout ??= numLit(0);
2912
+ headers ??= { kind: "arrayLit", elems: [], type: arrayOf(STRING), loc };
2913
+ const autoEnd = boolLit(member === "get", loc);
2914
+ if (connCb !== null) {
2915
+ const base = [connCb, path, method, timeout, headers, autoEnd];
2916
+ if (args.length === 1) {
2917
+ return { kind: "libCall", fn: "http.requestConn", args: base, type: HTTPCLIENTREQ_T, loc };
2918
+ }
2919
+ const { cb } = lowerCallbackArg(L, args[1], "response callbacks", 1, (p) => p.kind === "httpReq", "use (res) or ()", [HTTPREQ_T]);
2920
+ return { kind: "libCall", fn: "http.requestConnCb", args: [...base, cb], type: HTTPCLIENTREQ_T, loc };
2921
+ }
2922
+ const base = [host, port, path, method, timeout, headers, autoEnd];
2923
+ if (secureish) {
2924
+ reject ??= boolLit(true, loc); /* Node's default: verify */
2925
+ ca ??= strLit(""); /* none: /etc/ssl/cert.pem stands in for Node's roots */
2926
+ base.push(reject, ca);
2927
+ }
2928
+ if (binding !== null)
2929
+ base.unshift(secureExpr());
2930
+ if (args.length === 1) {
2931
+ const fn = binding !== null ? "https.requestFn" : secure === true ? "https.request" : "http.request";
2932
+ return { kind: "libCall", fn, args: base, type: HTTPCLIENTREQ_T, loc };
2933
+ }
2934
+ const { cb } = lowerCallbackArg(L, args[1], "response callbacks", 1, (p) => p.kind === "httpReq", "use (res) or ()", [HTTPREQ_T]);
2935
+ const fn = binding !== null ? "https.requestFnCb" : secure === true ? "https.requestCb" : "http.requestCb";
2936
+ return { kind: "libCall", fn, args: [...base, cb], type: HTTPCLIENTREQ_T, loc };
2937
+ }
2938
+ /** The headers option of http.request: an object literal with literal
2939
+ * keys and string values packs into the flat [k0, v0, k1, v1, ...] pairs
2940
+ * array directly; any other expression must be a Record whose values are
2941
+ * strings (or `string | undefined` — absent entries drop, the env-option
2942
+ * rule), flattened by the interned env.pairs helper. */
2943
+ function lowerClientHeadersOption(L, node) {
2944
+ const loc = locOf(node);
2945
+ if (ts.isObjectLiteralExpression(node)) {
2946
+ const elems = [];
2947
+ for (const prop of node.properties) {
2948
+ const key = ts.isPropertyAssignment(prop) ? staticHeaderKeyOf(L, prop) : null;
2949
+ if (!ts.isPropertyAssignment(prop) || key === null) {
2950
+ L.noLowering("request headers with dynamic keys, spreads, or shorthand entries", prop, "each header must be a `name: value` entry whose key is a literal (or a string-literal-typed const: [PORTLESS_HEADER])");
2951
+ }
2952
+ let value = L.lowerExpr(prop.initializer);
2953
+ if (value.type.kind === "f64") {
2954
+ // Node formats number values via String(n) — the same ToString.
2955
+ value = { kind: "toString", operand: value, type: STRING, loc };
2956
+ }
2957
+ if (value.type.kind !== "string") {
2958
+ L.noLowering(`request header values of type '${L.fmt(value.type)}'`, prop.initializer, "header values are strings or numbers here");
2959
+ }
2960
+ elems.push({ kind: "strLit", value: key, type: STRING, loc });
2961
+ elems.push(value);
2962
+ }
2963
+ return { kind: "arrayLit", elems, type: arrayOf(STRING), loc };
2964
+ }
2965
+ const v = L.lowerExpr(node);
2966
+ if (v.type.kind !== "record") {
2967
+ L.noLowering(`request headers of '${L.fmt(v.type)}' values`, node, "pass an object literal or a Record<string, string>");
2968
+ }
2969
+ const helper = L.envToPairsHelper(v.type.shapeId, loc);
2970
+ if (helper === null) {
2971
+ L.noLowering(`request headers of '${L.fmt(v.type)}' values`, node, "header values must be strings (or string | undefined)");
2972
+ }
2973
+ return { kind: "call", callee: helper, args: [v], type: arrayOf(STRING), loc };
2974
+ }
2975
+ /** Method calls on ClientRequest receivers: write/end/destroy and
2976
+ * on/once("response" | "error" | "timeout" | "close"). Null for other
2977
+ * receivers. */
2978
+ function lowerHttpClientMethodCall(L, call, access) {
2979
+ if (L.mapTypeOf(L.typeOf(access.expression))?.kind !== "httpClientReq")
2980
+ return null;
2981
+ if (!L.isStdlibMember(access))
2982
+ return null;
2983
+ const name = access.name.text;
2984
+ const loc = locOf(call);
2985
+ const args = call.arguments;
2986
+ if (name === "write" || name === "end") {
2987
+ requireStatementPosition(L, call, `request.${name}(...)`);
2988
+ const minArgs = name === "write" ? 1 : 0;
2989
+ if (args.length < minArgs || args.length > 1) {
2990
+ L.noLowering(`${name} with ${args.length} arguments`, call, name === "write" ? "the supported form is write(data)" : "the supported forms are end() and end(data)");
2991
+ }
2992
+ const receiver = L.lowerExpr(access.expression);
2993
+ if (args.length === 0) {
2994
+ return { kind: "libCall", fn: "http.clientEnd", args: [receiver], type: VOID, loc };
2995
+ }
2996
+ const data = L.lowerExpr(args[0]);
2997
+ if (data.type.kind === "string") {
2998
+ const fn = name === "write" ? "http.clientWrite" : "http.clientEndStr";
2999
+ return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc };
3000
+ }
3001
+ if (data.type.kind === "dyn") {
3002
+ // An untyped JS payload — the net.sockWriteDyn story.
3003
+ const fn = name === "write" ? "http.clientWriteDyn" : "http.clientEndDyn";
3004
+ return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc };
3005
+ }
3006
+ if (data.type.kind === "bytes" && data.type.elem === "u8") {
3007
+ const fn = name === "write" ? "http.clientWriteBytes" : "http.clientEndBytes";
3008
+ return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc };
3009
+ }
3010
+ L.noLowering(`${name} of '${L.fmt(data.type)}' data`, args[0] ?? call, NARROW_DATA_HINT);
3011
+ }
3012
+ if (name === "destroy") {
3013
+ requireStatementPosition(L, call, "request.destroy()");
3014
+ if (args.length !== 0) {
3015
+ L.noLowering(`destroy with ${args.length} arguments`, call, "destroy() takes no arguments here");
3016
+ }
3017
+ const receiver = L.lowerExpr(access.expression);
3018
+ return { kind: "libCall", fn: "http.clientDestroy", args: [receiver], type: VOID, loc };
3019
+ }
3020
+ if ((name === "on" || name === "once" || name === "addListener") && args.length === 2) {
3021
+ requireStatementPosition(L, call, `request.${name}(...)`);
3022
+ const once = boolLit(name === "once", loc);
3023
+ const evT = L.typeOf(args[0]);
3024
+ const event = evT.isStringLiteralType() ? evT.value : null;
3025
+ const receiver = L.lowerExpr(access.expression);
3026
+ if (event === "response") {
3027
+ const { cb } = lowerCallbackArg(L, args[1], "response listeners", 1, (p) => p.kind === "httpReq", "use (res) or ()", [HTTPREQ_T]);
3028
+ return { kind: "libCall", fn: "http.clientOnResponse", args: [receiver, cb, once], type: VOID, loc };
3029
+ }
3030
+ if (event === "error") {
3031
+ const { cb } = lowerCallbackArg(L, args[1], "error listeners", 1, (p) => p.kind === "object" && p.className === "%Error", "use (err) or ()", [ERROR_T]);
3032
+ return { kind: "libCall", fn: "http.clientOnError", args: [receiver, cb, once], type: VOID, loc };
3033
+ }
3034
+ if (event === "timeout" || event === "close") {
3035
+ const { cb } = lowerCallbackArg(L, args[1], `${event} listeners`, 0, () => false, "use ()", []);
3036
+ const fn = event === "timeout" ? "http.clientOnTimeout" : "http.clientOnClose";
3037
+ return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc };
3038
+ }
3039
+ if (event === "upgrade") {
3040
+ // The client half of the WebSocket handover: a 101 response fires
3041
+ // (res, socket, head) INSTEAD of 'response'; no listener destroys
3042
+ // the connection, Node's default.
3043
+ const cb = L.lowerExpr(args[1]);
3044
+ if (cb.type.kind !== "func" || cb.type.params.length > 3) {
3045
+ L.unsupported("SC1090", args[1], "upgrade listeners with more than three parameters (use (res, socket, head))");
3046
+ }
3047
+ if (cb.type.ret.kind !== "void") {
3048
+ L.unsupported("SC1090", args[1], "upgrade listeners returning a value (make the callback body a block)");
3049
+ }
3050
+ const [p0, p1, p2] = cb.type.params;
3051
+ if ((p0 !== undefined && p0.kind !== "httpReq") ||
3052
+ (p1 !== undefined && p1.kind !== "netSocket") ||
3053
+ (p2 !== undefined && !(p2.kind === "bytes" && p2.elem === "u8"))) {
3054
+ L.unsupported("SC1090", args[1], "upgrade listeners whose parameters are not (res: IncomingMessage, socket: Socket, head: Buffer)");
3055
+ }
3056
+ return { kind: "libCall", fn: "http.clientOnUpgrade", args: [receiver, cb, once], type: VOID, loc };
3057
+ }
3058
+ L.noLowering(`request.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, args[0], '"response", "upgrade", "error", "timeout", and "close" are the supported request events (as literals)');
3059
+ }
3060
+ L.noLowering(`ClientRequest.${name}`, call, "write, end, destroy, destroyed, and on/once of response/error/timeout/close are the supported ClientRequest members", L.checker.getSymbolAtLocation(access.name));
3061
+ }
3062
+ /** True when `node` is the h2-only stream/session CALL shape —
3063
+ * `req.stream.destroy()` / `req.session.m(...)` on an identifier
3064
+ * receiver mapped httpReq (the lower-calls streamUndefCall precedent's
3065
+ * predicate, shared): on this lowering the member is always undefined,
3066
+ * so the CALL always throws — a concise arrow over it (`() =>
3067
+ * req.stream.destroy()`) is a throw-only body whose declared return type
3068
+ * (ServerHttp2Stream, unmappable) must not decide the lambda's ABI.
3069
+ * lambdaSignature consults this and takes void, the `never` stance. */
3070
+ export function isStreamUndefCallExpr(L, node) {
3071
+ return (ts.isCallExpression(node) &&
3072
+ ts.isPropertyAccessExpression(node.expression) &&
3073
+ ts.isPropertyAccessExpression(node.expression.expression) &&
3074
+ !node.expression.expression.questionDotToken &&
3075
+ !node.expression.questionDotToken &&
3076
+ (node.expression.expression.name.text === "stream" ||
3077
+ node.expression.expression.name.text === "session") &&
3078
+ ts.isIdentifier(node.expression.expression.expression) &&
3079
+ L.mapTypeOf(L.typeOf(node.expression.expression.expression))?.kind === "httpReq" &&
3080
+ L.isStdlibMember(node.expression.expression));
3081
+ }
3082
+ /** `x instanceof net.Socket` over a UNION-typed x with a netSocket arm —
3083
+ * the h2 compat 'connect' listener's narrowing (`resOrSocket instanceof
3084
+ * net.Socket`): one runtime tag test; tsc's control-flow narrowing types
3085
+ * the branches and reads bridge through maybeNarrow's unionNarrow. Null
3086
+ * for every other shape (lower-exprs' fences stand). */
3087
+ export function lowerSocketInstanceOf(L, expr, loc) {
3088
+ const rhs = expr.right;
3089
+ const isNetSocketRef = (ts.isPropertyAccessExpression(rhs) &&
3090
+ rhs.name.text === "Socket" &&
3091
+ L.builtinMemberOf(rhs)?.module === "net") ||
3092
+ (ts.isIdentifier(rhs) && L.builtinImportOf(rhs)?.module === "net" &&
3093
+ L.builtinImportOf(rhs)?.member === "Socket");
3094
+ if (!isNetSocketRef)
3095
+ return null;
3096
+ const leftT = L.mapTypeOf(L.typeOf(expr.left));
3097
+ if (leftT?.kind !== "union")
3098
+ return null;
3099
+ const def = L.unions.get(leftT.unionId);
3100
+ const tag = def ? def.arms.findIndex((a) => a.kind === "netSocket") : -1;
3101
+ if (tag < 0)
3102
+ return null;
3103
+ const left = L.lowerExpr(expr.left);
3104
+ if (left.type.kind !== "union" || left.type.unionId !== leftT.unionId)
3105
+ return null;
3106
+ return { kind: "unionIsTag", unionId: leftT.unionId, tag, negated: false, value: left, type: BOOL, loc };
3107
+ }
3108
+ /** True when `node` reads `.headers` off an IncomingMessage — the
3109
+ * receiver shape of both header-read forms. */
3110
+ export function isHttpReqHeaders(L, node) {
3111
+ return (ts.isPropertyAccessExpression(node) &&
3112
+ !node.questionDotToken &&
3113
+ node.name.text === "headers" &&
3114
+ L.mapTypeOf(L.typeOf(node.expression))?.kind === "httpReq" &&
3115
+ L.isStdlibMember(node));
3116
+ }
3117
+ /** `req.headers["x-name"]` — the element twin of the property read below;
3118
+ * called from lowerElementAccess (the process.env precedent). Answers the
3119
+ * interned `string | undefined` union; names match case-insensitively
3120
+ * (the runtime stores them lowercased, like Node). */
3121
+ export function lowerHttpHeadersElement(L, expr) {
3122
+ if (!isHttpReqHeaders(L, expr.expression))
3123
+ return null;
3124
+ const recv = expr.expression.expression;
3125
+ const key = L.lowerExpr(expr.argumentExpression);
3126
+ if (key.type.kind !== "string") {
3127
+ L.unsupported("SC1090", expr.argumentExpression, "indexing req.headers with non-string keys");
3128
+ }
3129
+ const receiver = L.lowerExpr(recv);
3130
+ return {
3131
+ kind: "libCall",
3132
+ fn: "http.reqHeader",
3133
+ args: [receiver, key],
3134
+ type: L.envValueType(),
3135
+ loc: locOf(expr),
3136
+ };
3137
+ }
3138
+ /** Method calls on IncomingMessage receivers: on/once("data" | "end").
3139
+ * Null for other receivers. */
3140
+ function lowerHttpReqMethodCall(L, call, access) {
3141
+ if (L.mapTypeOf(L.typeOf(access.expression))?.kind !== "httpReq")
3142
+ return null;
3143
+ if (!L.isStdlibMember(access))
3144
+ return null;
3145
+ const name = access.name.text;
3146
+ const loc = locOf(call);
3147
+ const args = call.arguments;
3148
+ if (name === "resume" || name === "destroy") {
3149
+ requireStatementPosition(L, call, `req.${name}()`);
3150
+ if (args.length !== 0) {
3151
+ L.noLowering(`${name} with ${args.length} arguments`, call, `${name}() takes no arguments here`);
3152
+ }
3153
+ const receiver = handleReceiver(L, access.expression, HTTPREQ_T);
3154
+ const fn = name === "resume" ? "http.reqResume" : "http.reqDestroy";
3155
+ return { kind: "libCall", fn, args: [receiver], type: VOID, loc };
3156
+ }
3157
+ if (name === "setEncoding") {
3158
+ // setEncoding('utf8') — 'data' delivers strings (the chunk-encoding
3159
+ // window); other real encodings fence loudly at runtime, unknown
3160
+ // names throw Node's ERR_UNKNOWN_ENCODING.
3161
+ requireStatementPosition(L, call, "req.setEncoding(...)");
3162
+ if (args.length !== 1) {
3163
+ L.noLowering(`setEncoding with ${args.length} arguments`, call, "the supported form is setEncoding(encoding)");
3164
+ }
3165
+ const receiver = handleReceiver(L, access.expression, HTTPREQ_T);
3166
+ const enc = L.lowerExprExpecting(args[0], STRING);
3167
+ return { kind: "libCall", fn: "http.reqSetEncoding", args: [receiver, enc], type: VOID, loc };
3168
+ }
3169
+ if (name === "pipe") {
3170
+ // The proxy legs: req→res (the response body forward), req→clientReq
3171
+ // (the request body forward), req→socket (the upgrade-rejection
3172
+ // write) — plus socket→socket, which lives on the socket receiver.
3173
+ requireStatementPosition(L, call, "req.pipe(...)");
3174
+ if (args.length !== 1) {
3175
+ L.noLowering(`pipe with ${args.length} arguments`, call, "the supported form is pipe(destination)");
3176
+ }
3177
+ const receiver = handleReceiver(L, access.expression, HTTPREQ_T);
3178
+ const dst = L.lowerExpr(args[0]);
3179
+ const fn = dst.type.kind === "httpRes" ? "http.reqPipeRes"
3180
+ : dst.type.kind === "httpClientReq" ? "http.reqPipeClient"
3181
+ : dst.type.kind === "netSocket" ? "http.reqPipeSock"
3182
+ : null;
3183
+ if (fn === null) {
3184
+ L.noLowering(`pipe into '${L.fmt(dst.type)}' destinations`, args[0], "an IncomingMessage pipes into a ServerResponse, a ClientRequest, or a Socket");
3185
+ }
3186
+ return { kind: "libCall", fn, args: [receiver, dst], type: VOID, loc };
3187
+ }
3188
+ if ((name === "on" || name === "once" || name === "addListener") && args.length === 2) {
3189
+ requireStatementPosition(L, call, `req.${name}(...)`);
3190
+ const once = boolLit(name === "once", loc);
3191
+ const evT = L.typeOf(args[0]);
3192
+ const event = evT.isStringLiteralType() ? evT.value : null;
3193
+ const receiver = handleReceiver(L, access.expression, HTTPREQ_T);
3194
+ if (event === "data") {
3195
+ const { cb } = lowerCallbackArg(L, args[1], "data listeners", 1, (p) => p.kind === "bytes" && p.elem === "u8", "use (chunk: Buffer) or ()", [DYN]);
3196
+ return { kind: "libCall", fn: "http.reqOnData", args: [receiver, cb, once], type: VOID, loc };
3197
+ }
3198
+ if (event === "end") {
3199
+ const { cb } = lowerCallbackArg(L, args[1], "end listeners", 0, () => false, "use ()", []);
3200
+ return { kind: "libCall", fn: "http.reqOnEnd", args: [receiver, cb, once], type: VOID, loc };
3201
+ }
3202
+ if (event === "error") {
3203
+ const { cb } = lowerCallbackArg(L, args[1], "error listeners", 1, (p) => p.kind === "object" && p.className === "%Error", "use (err) or ()", [ERROR_T]);
3204
+ return { kind: "libCall", fn: "http.reqOnError", args: [receiver, cb, once], type: VOID, loc };
3205
+ }
3206
+ if (event === "close") {
3207
+ const { cb } = lowerCallbackArg(L, args[1], "close listeners", 0, () => false, "use ()", []);
3208
+ return { kind: "libCall", fn: "http.reqOnClose", args: [receiver, cb, once], type: VOID, loc };
3209
+ }
3210
+ if (event === "aborted") {
3211
+ // The h2 compat event (Http2ServerRequest 'aborted'); an http/1
3212
+ // request registers too and never fires — the parser lane's story.
3213
+ const { cb } = lowerCallbackArg(L, args[1], "aborted listeners", 0, () => false, "use ()", []);
3214
+ return { kind: "libCall", fn: "http.reqOnAborted", args: [receiver, cb, once], type: VOID, loc };
3215
+ }
3216
+ L.noLowering(`req.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, args[0], '"data", "end", "error", "close", and "aborted" are the supported request events (as literals)');
3217
+ }
3218
+ L.noLowering(`IncomingMessage.${name}`, call, "url, method, statusCode, statusMessage, socket, headers/rawHeaders reads, resume, destroy, pipe, and on/once of data/end/error/close are the supported IncomingMessage members", L.checker.getSymbolAtLocation(access.name));
3219
+ }
3220
+ /** Method calls on ServerResponse receivers: setHeader/writeHead/write/
3221
+ * end. Null for other receivers. */
3222
+ function lowerHttpResMethodCall(L, call, access) {
3223
+ if (L.mapTypeOf(L.typeOf(access.expression))?.kind !== "httpRes")
3224
+ return null;
3225
+ if (!L.isStdlibMember(access))
3226
+ return null;
3227
+ const name = access.name.text;
3228
+ const loc = locOf(call);
3229
+ const args = call.arguments;
3230
+ if (name === "setHeader") {
3231
+ requireStatementPosition(L, call, "res.setHeader(...)");
3232
+ if (args.length !== 2) {
3233
+ L.noLowering(`setHeader with ${args.length} arguments`, call, "the supported form is setHeader(name, value)");
3234
+ }
3235
+ const receiver = handleReceiver(L, access.expression, HTTPRES_T);
3236
+ const header = L.lowerExprExpecting(args[0], STRING);
3237
+ let value = L.lowerExpr(args[1]);
3238
+ if (value.type.kind === "f64") {
3239
+ // Node formats number values via String(n) — the same ToString.
3240
+ value = { kind: "toString", operand: value, type: STRING, loc };
3241
+ }
3242
+ if (value.type.kind !== "string") {
3243
+ L.noLowering(`setHeader with a '${L.fmt(value.type)}' value`, args[1], "header values are strings or numbers here");
3244
+ }
3245
+ return { kind: "libCall", fn: "http.resSetHeader", args: [receiver, header, value], type: VOID, loc };
3246
+ }
3247
+ if (name === "writeHead") {
3248
+ if (args.length < 1 || args.length > 3) {
3249
+ L.noLowering(`writeHead with ${args.length} arguments`, call, "the supported forms are writeHead(status[, statusMessage][, headers]) — headers as a literal object, a Record, or a flat [name, value, ...] array");
3250
+ }
3251
+ const receiver = handleReceiver(L, access.expression, HTTPRES_T);
3252
+ const status = L.lowerExprExpecting(args[0], F64);
3253
+ // The optional statusMessage: a string-typed second argument is the
3254
+ // reason phrase (Node's overload split); it rides the composing
3255
+ // helper as a resStatusMsgSet prefix before the head goes out.
3256
+ let msg = null;
3257
+ let headersNode;
3258
+ if (args.length === 2) {
3259
+ if (L.mapTypeOf(L.typeOf(args[1]))?.kind === "string")
3260
+ msg = L.lowerExprExpecting(args[1], STRING);
3261
+ else
3262
+ headersNode = args[1];
3263
+ }
3264
+ else if (args.length === 3) {
3265
+ msg = L.lowerExprExpecting(args[1], STRING);
3266
+ headersNode = args[2];
3267
+ }
3268
+ // Node answers the response (`return this` — the chaining shape
3269
+ // res.writeHead(...).end(...)): statement position keeps the plain
3270
+ // void call; a consumed result (or a statusMessage prefix) rides the
3271
+ // receiver-returning helper.
3272
+ const headResult = (fn, callArgs) => {
3273
+ if (msg === null && ts.isExpressionStatement(call.parent)) {
3274
+ return { kind: "libCall", fn, args: callArgs, type: VOID, loc };
3275
+ }
3276
+ if (msg === null)
3277
+ return receiverReturningCall(L, fn, callArgs, HTTPRES_T, loc);
3278
+ // With a message the helper's parameter list gains the msg at slot
3279
+ // 1 (feeding the resStatusMsgSet prefix); the head call itself
3280
+ // re-selects around it.
3281
+ const withMsg = [callArgs[0], msg, ...callArgs.slice(1)];
3282
+ return receiverReturningCall(L, fn, withMsg, HTTPRES_T, loc, {
3283
+ prefix: { fn: "http.resStatusMsgSet", argIndices: [0, 1] },
3284
+ mainArgIndices: [0, ...callArgs.slice(1).map((_, i) => i + 2)],
3285
+ });
3286
+ };
3287
+ if (headersNode === undefined) {
3288
+ return headResult("http.resWriteHead", [receiver, status]);
3289
+ }
3290
+ // The headers argument: an OBJECT LITERAL with identifier or
3291
+ // string-literal keys and string-typed values, packed into parallel
3292
+ // name/value arrays (evaluation order is the literal's own) — any
3293
+ // RECORD whose values are strings / `string | undefined` (absent
3294
+ // entries drop), flattened through the interned env.pairs helper —
3295
+ // or Node's FLAT ARRAY form ([name, value, name, value, ...]: a
3296
+ // string[] value, repeated names writing one line each).
3297
+ if (!ts.isObjectLiteralExpression(headersNode)) {
3298
+ const v = L.lowerExpr(headersNode);
3299
+ if (v.type.kind === "array" && v.type.elem.kind === "string") {
3300
+ return headResult("http.resWriteHeadPairs", [receiver, status, v]);
3301
+ }
3302
+ if (v.type.kind === "dyn") {
3303
+ // A checked-dynamic headers value (an untyped JS helper's
3304
+ // parameter — the suite's test(headers) idiom): the runtime OBJ
3305
+ // walk sets each entry, loud fences for non-string/number values.
3306
+ return headResult("http.resWriteHeadDyn", [receiver, status, v]);
3307
+ }
3308
+ if (v.type.kind !== "record") {
3309
+ L.noLowering(`writeHead with a '${L.fmt(v.type)}' headers argument`, headersNode, "pass an object literal, a Record<string, string>, or a flat [name, value, ...] string array — or call setHeader per entry before writeHead(status)");
3310
+ }
3311
+ const helper = L.envToPairsHelper(v.type.shapeId, locOf(headersNode));
3312
+ if (helper === null) {
3313
+ L.noLowering(`writeHead headers of '${L.fmt(v.type)}' values`, headersNode, "header values must be strings (or string | undefined) — convert numbers with String(n)");
3314
+ }
3315
+ const pairs = { kind: "call", callee: helper, args: [v], type: arrayOf(STRING), loc };
3316
+ return headResult("http.resWriteHeadPairs", [receiver, status, pairs]);
3317
+ }
3318
+ const names = [];
3319
+ const values = [];
3320
+ for (const prop of headersNode.properties) {
3321
+ const key = ts.isPropertyAssignment(prop) ? staticHeaderKeyOf(L, prop) : null;
3322
+ if (!ts.isPropertyAssignment(prop) || key === null) {
3323
+ L.noLowering("writeHead headers with dynamic keys, spreads, or shorthand entries", prop, "each header must be a `name: value` entry whose key is a literal (or a string-literal-typed const: [PORTLESS_HEADER])");
3324
+ }
3325
+ let value = L.lowerExpr(prop.initializer);
3326
+ if (value.type.kind === "f64") {
3327
+ // Node formats number values via String(n) — the same ToString.
3328
+ value = { kind: "toString", operand: value, type: STRING, loc };
3329
+ }
3330
+ if (value.type.kind !== "string") {
3331
+ L.noLowering(`writeHead header values of type '${L.fmt(value.type)}'`, prop.initializer, "header values are strings or numbers here");
3332
+ }
3333
+ names.push({ kind: "strLit", value: key, type: STRING, loc });
3334
+ values.push(value);
3335
+ }
3336
+ const namesArr = { kind: "arrayLit", elems: names, type: arrayOf(STRING), loc };
3337
+ const valuesArr = { kind: "arrayLit", elems: values, type: arrayOf(STRING), loc };
3338
+ return headResult("http.resWriteHeadN", [receiver, status, namesArr, valuesArr]);
3339
+ }
3340
+ if (name === "write" || name === "end") {
3341
+ requireStatementPosition(L, call, `res.${name}(...)`);
3342
+ const minArgs = name === "write" ? 1 : 0;
3343
+ const maxArgs = name === "write" ? 1 : 2;
3344
+ if (args.length < minArgs || args.length > maxArgs) {
3345
+ L.noLowering(`${name} with ${args.length} arguments`, call, name === "write"
3346
+ ? "the supported form is write(data)"
3347
+ : "the supported forms are end(), end(data), end(callback), and end(data, callback)");
3348
+ }
3349
+ const receiver = handleReceiver(L, access.expression, HTTPRES_T);
3350
+ // end's callback forms — end(cb) / end(data, cb): the callback fires
3351
+ // deferred once the body went out (Node's 'finish' emit), through
3352
+ // the resOnFinish slot registered just before the end call in an
3353
+ // interned composing helper.
3354
+ let cbArg = null;
3355
+ let dataNode = args[0];
3356
+ if (name === "end" && args.length === 2) {
3357
+ const { cb } = lowerCallbackArg(L, args[1], "end callbacks", 0, () => false, "use ()", []);
3358
+ cbArg = cb;
3359
+ }
3360
+ else if (name === "end" && args.length === 1 &&
3361
+ L.mapTypeOf(L.typeOf(args[0]))?.kind !== "string" &&
3362
+ L.mapTypeOf(L.typeOf(args[0]))?.kind !== "bytes") {
3363
+ const probe = L.typeOf(args[0]);
3364
+ if (L.mapTypeOf(probe)?.kind === "func" || L.mapTypeOf(probe) === null || L.mapTypeOf(probe)?.kind === "dyn") {
3365
+ const { cb } = lowerCallbackArg(L, args[0], "end callbacks", 0, () => false, "use ()", []);
3366
+ cbArg = cb;
3367
+ dataNode = undefined;
3368
+ }
3369
+ }
3370
+ const endWithCb = (fn, callArgs, cb) => {
3371
+ const all = [...callArgs, cb];
3372
+ const key = `server.endcb:${fn}:${all.map((a) => typeKey(a.type)).join(",")}`;
3373
+ const existing = L.arrHofHelpers.get(key);
3374
+ const helper = existing ?? `%server.endcb.${L.arrHofHelpers.size}`;
3375
+ if (!existing) {
3376
+ L.arrHofHelpers.set(key, helper);
3377
+ const params = all.map((a, i) => ({ localId: `p${i}.0`, name: `p${i}`, type: a.type }));
3378
+ const ref = (i) => ({ kind: "varRef", localId: params[i].localId, type: params[i].type, loc });
3379
+ L.liftedFns.push({
3380
+ name: helper,
3381
+ params,
3382
+ returnType: VOID,
3383
+ locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
3384
+ body: [
3385
+ {
3386
+ kind: "exprStmt",
3387
+ expr: { kind: "libCall", fn: "http.resOnFinish", args: [ref(0), ref(all.length - 1)], type: VOID, loc },
3388
+ loc,
3389
+ },
3390
+ {
3391
+ kind: "exprStmt",
3392
+ expr: { kind: "libCall", fn, args: callArgs.map((_, i) => ref(i)), type: VOID, loc },
3393
+ loc,
3394
+ },
3395
+ ],
3396
+ loc,
3397
+ });
3398
+ }
3399
+ return { kind: "call", callee: helper, args: all, type: VOID, loc };
3400
+ };
3401
+ if (dataNode === undefined) {
3402
+ if (cbArg !== null)
3403
+ return endWithCb("http.resEnd", [receiver], cbArg);
3404
+ return { kind: "libCall", fn: "http.resEnd", args: [receiver], type: VOID, loc };
3405
+ }
3406
+ const data = L.lowerExpr(dataNode);
3407
+ if (data.type.kind === "string") {
3408
+ const fn = name === "write" ? "http.resWrite" : "http.resEndStr";
3409
+ if (cbArg !== null)
3410
+ return endWithCb(fn, [receiver, data], cbArg);
3411
+ return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc };
3412
+ }
3413
+ if (data.type.kind === "bytes" && data.type.elem === "u8") {
3414
+ const fn = name === "write" ? "http.resWriteBytes" : "http.resEndBytes";
3415
+ if (cbArg !== null)
3416
+ return endWithCb(fn, [receiver, data], cbArg);
3417
+ return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc };
3418
+ }
3419
+ if (data.type.kind === "dyn") {
3420
+ // An untyped JS payload — the net.sockWriteDyn story.
3421
+ const fn = name === "write" ? "http.resWriteDyn" : "http.resEndDyn";
3422
+ if (cbArg !== null)
3423
+ return endWithCb(fn, [receiver, data], cbArg);
3424
+ return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc };
3425
+ }
3426
+ L.noLowering(`${name} of '${L.fmt(data.type)}' data`, dataNode ?? call, NARROW_DATA_HINT);
3427
+ }
3428
+ if (name === "getHeader" || name === "hasHeader" || name === "removeHeader") {
3429
+ // The header CRUD trio (setHeader's readers): getHeader answers the
3430
+ // value as SET — `string | undefined` here, where Node can also
3431
+ // answer numbers/arrays it was handed (this surface stores strings);
3432
+ // removeHeader drops every case-insensitive match before the head
3433
+ // goes out; hasHeader is the boolean probe.
3434
+ if (name === "removeHeader")
3435
+ requireStatementPosition(L, call, "res.removeHeader(...)");
3436
+ if (args.length !== 1) {
3437
+ L.noLowering(`${name} with ${args.length} arguments`, call, `the supported form is ${name}(name)`);
3438
+ }
3439
+ const receiver = handleReceiver(L, access.expression, HTTPRES_T);
3440
+ const header = L.lowerExprExpecting(args[0], STRING);
3441
+ if (name === "getHeader") {
3442
+ return { kind: "libCall", fn: "http.resGetHeader", args: [receiver, header], type: L.envValueType(), loc };
3443
+ }
3444
+ if (name === "hasHeader") {
3445
+ return { kind: "libCall", fn: "http.resHasHeader", args: [receiver, header], type: BOOL, loc };
3446
+ }
3447
+ return { kind: "libCall", fn: "http.resRemoveHeader", args: [receiver, header], type: VOID, loc };
3448
+ }
3449
+ if (name === "destroy") {
3450
+ requireStatementPosition(L, call, "res.destroy()");
3451
+ if (args.length !== 0) {
3452
+ L.noLowering(`destroy with ${args.length} arguments`, call, "destroy() takes no arguments here");
3453
+ }
3454
+ const receiver = handleReceiver(L, access.expression, HTTPRES_T);
3455
+ return { kind: "libCall", fn: "http.resDestroy", args: [receiver], type: VOID, loc };
3456
+ }
3457
+ if ((name === "on" || name === "once" || name === "addListener") && args.length === 2) {
3458
+ requireStatementPosition(L, call, `res.${name}(...)`);
3459
+ const once = boolLit(name === "once", loc);
3460
+ const evT = L.typeOf(args[0]);
3461
+ const event = evT.isStringLiteralType() ? evT.value : null;
3462
+ const receiver = handleReceiver(L, access.expression, HTTPRES_T);
3463
+ if (event === "close") {
3464
+ const { cb } = lowerCallbackArg(L, args[1], "close listeners", 0, () => false, "use ()", []);
3465
+ return { kind: "libCall", fn: "http.resOnClose", args: [receiver, cb, once], type: VOID, loc };
3466
+ }
3467
+ L.noLowering(`res.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, args[0], '"close" is the supported response event (as a literal)');
3468
+ }
3469
+ L.noLowering(`ServerResponse.${name}`, call, "setHeader, getHeader, hasHeader, removeHeader, writeHead, write, end, destroy, headersSent, statusCode, statusMessage, and on/once of close are the supported ServerResponse members", L.checker.getSymbolAtLocation(access.name));
3470
+ }
3471
+ //# sourceMappingURL=lower-server.js.map