@scriptc/compiler 0.1.2 → 0.1.4

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/ambient/scriptc-node-fallback.d.ts +258 -64
  2. package/dist/backend/c/async.d.ts +30 -0
  3. package/dist/backend/c/async.js +224 -0
  4. package/dist/backend/c/async.js.map +1 -1
  5. package/dist/backend/c/c-emitter.d.ts +13 -0
  6. package/dist/backend/c/c-emitter.js +28 -9
  7. package/dist/backend/c/c-emitter.js.map +1 -1
  8. package/dist/backend/c/exprs.d.ts +8 -1
  9. package/dist/backend/c/exprs.js +445 -60
  10. package/dist/backend/c/exprs.js.map +1 -1
  11. package/dist/backend/c/shapes.js +15 -13
  12. package/dist/backend/c/shapes.js.map +1 -1
  13. package/dist/backend/c/types.d.ts +4 -0
  14. package/dist/backend/c/types.js +20 -1
  15. package/dist/backend/c/types.js.map +1 -1
  16. package/dist/backend/c/types.test.js +8 -1
  17. package/dist/backend/c/types.test.js.map +1 -1
  18. package/dist/backend/c/walkers.js +32 -11
  19. package/dist/backend/c/walkers.js.map +1 -1
  20. package/dist/backend/json-literal.d.ts +7 -0
  21. package/dist/backend/json-literal.js +17 -0
  22. package/dist/backend/json-literal.js.map +1 -0
  23. package/dist/backend/json-literal.test.d.ts +1 -0
  24. package/dist/backend/json-literal.test.js +17 -0
  25. package/dist/backend/json-literal.test.js.map +1 -0
  26. package/dist/backend/link-plan.d.ts +2 -1
  27. package/dist/backend/link-plan.js +6 -1
  28. package/dist/backend/link-plan.js.map +1 -1
  29. package/dist/backend/llvm/classes.js +8 -7
  30. package/dist/backend/llvm/classes.js.map +1 -1
  31. package/dist/backend/llvm/common.d.ts +4 -0
  32. package/dist/backend/llvm/common.js +6 -0
  33. package/dist/backend/llvm/common.js.map +1 -1
  34. package/dist/backend/llvm/common.test.d.ts +1 -0
  35. package/dist/backend/llvm/common.test.js +9 -0
  36. package/dist/backend/llvm/common.test.js.map +1 -0
  37. package/dist/backend/llvm/dyn.d.ts +4 -0
  38. package/dist/backend/llvm/dyn.js +24 -13
  39. package/dist/backend/llvm/dyn.js.map +1 -1
  40. package/dist/backend/llvm/emitter.js +54 -12
  41. package/dist/backend/llvm/emitter.js.map +1 -1
  42. package/dist/backend/llvm/expr-bytes.d.ts +1 -1
  43. package/dist/backend/llvm/expr-bytes.js +8 -8
  44. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  45. package/dist/backend/llvm/expr-callbacks.d.ts +9 -0
  46. package/dist/backend/llvm/expr-callbacks.js +125 -0
  47. package/dist/backend/llvm/expr-callbacks.js.map +1 -1
  48. package/dist/backend/llvm/expr-context.d.ts +18 -2
  49. package/dist/backend/llvm/expr-dispatch.js +1 -0
  50. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  51. package/dist/backend/llvm/expr-dynamic.js +9 -0
  52. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  53. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  54. package/dist/backend/llvm/expr-primitives.js +38 -9
  55. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  56. package/dist/backend/llvm/expr-primitives.test.d.ts +1 -0
  57. package/dist/backend/llvm/expr-primitives.test.js +38 -0
  58. package/dist/backend/llvm/expr-primitives.test.js.map +1 -0
  59. package/dist/backend/llvm/expr-records.js +3 -3
  60. package/dist/backend/llvm/expr-records.js.map +1 -1
  61. package/dist/backend/llvm/expr-stream-bridges.js +2 -1
  62. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  63. package/dist/backend/llvm/expr-stream-callbacks.d.ts +6 -0
  64. package/dist/backend/llvm/expr-stream-callbacks.js +122 -0
  65. package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
  66. package/dist/backend/llvm/lib-dispatch.js +22 -0
  67. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  68. package/dist/backend/llvm/lib-filesystem.js +68 -10
  69. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  70. package/dist/backend/llvm/lib-network.js +18 -2
  71. package/dist/backend/llvm/lib-network.js.map +1 -1
  72. package/dist/backend/llvm/lib-process.js +206 -11
  73. package/dist/backend/llvm/lib-process.js.map +1 -1
  74. package/dist/backend/llvm/lib-shared.js +82 -0
  75. package/dist/backend/llvm/lib-shared.js.map +1 -1
  76. package/dist/backend/llvm/lib-stream.js +23 -0
  77. package/dist/backend/llvm/lib-stream.js.map +1 -1
  78. package/dist/backend/llvm/shapes.js +10 -8
  79. package/dist/backend/llvm/shapes.js.map +1 -1
  80. package/dist/backend/llvm/walkers.js +7 -5
  81. package/dist/backend/llvm/walkers.js.map +1 -1
  82. package/dist/backend/native-link-info.d.ts +2 -1
  83. package/dist/backend/native-link-info.js +4 -0
  84. package/dist/backend/native-link-info.js.map +1 -1
  85. package/dist/backend/native-link-info.test.js +24 -1
  86. package/dist/backend/native-link-info.test.js.map +1 -1
  87. package/dist/backend/native-toolchain.d.ts +11 -6
  88. package/dist/backend/native-toolchain.js +30 -13
  89. package/dist/backend/native-toolchain.js.map +1 -1
  90. package/dist/backend/native-toolchain.test.js +36 -1
  91. package/dist/backend/native-toolchain.test.js.map +1 -1
  92. package/dist/backend/runtime-pack-targets.test.js +1 -1
  93. package/dist/backend/runtime-pack-targets.test.js.map +1 -1
  94. package/dist/backend/runtime-pack.test.js +29 -10
  95. package/dist/backend/runtime-pack.test.js.map +1 -1
  96. package/dist/backend/targets.d.ts +9 -0
  97. package/dist/backend/targets.js +22 -0
  98. package/dist/backend/targets.js.map +1 -1
  99. package/dist/backend/targets.test.js +13 -1
  100. package/dist/backend/targets.test.js.map +1 -1
  101. package/dist/coverage/surface-manifest.js +9 -0
  102. package/dist/coverage/surface-manifest.js.map +1 -1
  103. package/dist/diagnostics/diagnostic.js +4 -4
  104. package/dist/diagnostics/diagnostic.js.map +1 -1
  105. package/dist/executable/executable-cache.d.ts +3 -0
  106. package/dist/executable/executable-cache.js +4 -0
  107. package/dist/executable/executable-cache.js.map +1 -1
  108. package/dist/executable/executable-cache.test.js +1 -0
  109. package/dist/executable/executable-cache.test.js.map +1 -1
  110. package/dist/frontend/fork-target.d.ts +17 -0
  111. package/dist/frontend/fork-target.js +256 -0
  112. package/dist/frontend/fork-target.js.map +1 -0
  113. package/dist/frontend/lowering/abstract-equality.d.ts +6 -0
  114. package/dist/frontend/lowering/abstract-equality.js +250 -0
  115. package/dist/frontend/lowering/abstract-equality.js.map +1 -0
  116. package/dist/frontend/lowering/containers/bytes.d.ts +0 -4
  117. package/dist/frontend/lowering/containers/bytes.js +112 -28
  118. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  119. package/dist/frontend/lowering/lower-assert.js +87 -12
  120. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  121. package/dist/frontend/lowering/lower-builtins.d.ts +72 -44
  122. package/dist/frontend/lowering/lower-builtins.js +1323 -165
  123. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  124. package/dist/frontend/lowering/lower-calls.d.ts +37 -27
  125. package/dist/frontend/lowering/lower-calls.js +682 -132
  126. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  127. package/dist/frontend/lowering/lower-classes.js +39 -8
  128. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  129. package/dist/frontend/lowering/lower-containers.js +162 -8
  130. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  131. package/dist/frontend/lowering/lower-event-emitter.js +212 -21
  132. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  133. package/dist/frontend/lowering/lower-exprs.js +238 -87
  134. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  135. package/dist/frontend/lowering/lower-inspect.d.ts +4 -0
  136. package/dist/frontend/lowering/lower-inspect.js +8 -1
  137. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  138. package/dist/frontend/lowering/lower-island.d.ts +6 -3
  139. package/dist/frontend/lowering/lower-island.js +165 -6
  140. package/dist/frontend/lowering/lower-island.js.map +1 -1
  141. package/dist/frontend/lowering/lower-modules.d.ts +6 -0
  142. package/dist/frontend/lowering/lower-modules.js +192 -38
  143. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  144. package/dist/frontend/lowering/lower-namespaces.js +7 -0
  145. package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
  146. package/dist/frontend/lowering/lower-server.d.ts +1 -1
  147. package/dist/frontend/lowering/lower-server.js +130 -27
  148. package/dist/frontend/lowering/lower-server.js.map +1 -1
  149. package/dist/frontend/lowering/lower-stmts.d.ts +5 -4
  150. package/dist/frontend/lowering/lower-stmts.js +141 -21
  151. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  152. package/dist/frontend/lowering/lower-stream.d.ts +3 -0
  153. package/dist/frontend/lowering/lower-stream.js +7 -0
  154. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  155. package/dist/frontend/lowering/lowerer.d.ts +90 -13
  156. package/dist/frontend/lowering/lowerer.js +416 -76
  157. package/dist/frontend/lowering/lowerer.js.map +1 -1
  158. package/dist/frontend/lowering/surfaces.d.ts +31 -3
  159. package/dist/frontend/lowering/surfaces.js +173 -90
  160. package/dist/frontend/lowering/surfaces.js.map +1 -1
  161. package/dist/frontend/npm-static-declarations.d.ts +4 -0
  162. package/dist/frontend/npm-static-declarations.js +83 -14
  163. package/dist/frontend/npm-static-declarations.js.map +1 -1
  164. package/dist/frontend/npm-static-declarations.test.js +33 -2
  165. package/dist/frontend/npm-static-declarations.test.js.map +1 -1
  166. package/dist/frontend/npm-static.js +6 -5
  167. package/dist/frontend/npm-static.js.map +1 -1
  168. package/dist/frontend/npm.d.ts +15 -3
  169. package/dist/frontend/npm.js +80 -8
  170. package/dist/frontend/npm.js.map +1 -1
  171. package/dist/frontend/npm.test.js +76 -9
  172. package/dist/frontend/npm.test.js.map +1 -1
  173. package/dist/frontend/program.js +110 -12
  174. package/dist/frontend/program.js.map +1 -1
  175. package/dist/frontend/resolve.d.ts +28 -7
  176. package/dist/frontend/resolve.js +90 -26
  177. package/dist/frontend/resolve.js.map +1 -1
  178. package/dist/frontend/resolve.test.js +21 -1
  179. package/dist/frontend/resolve.test.js.map +1 -1
  180. package/dist/frontend/type-mapper.d.ts +5 -0
  181. package/dist/frontend/type-mapper.js +115 -33
  182. package/dist/frontend/type-mapper.js.map +1 -1
  183. package/dist/index.d.ts +5 -0
  184. package/dist/index.js +30 -3
  185. package/dist/index.js.map +1 -1
  186. package/dist/ir/analysis.js +1 -0
  187. package/dist/ir/analysis.js.map +1 -1
  188. package/dist/ir/ir.d.ts +155 -66
  189. package/dist/ir/ir.js +143 -6
  190. package/dist/ir/ir.js.map +1 -1
  191. package/dist/ir/ir.test.js +14 -1
  192. package/dist/ir/ir.test.js.map +1 -1
  193. package/dist/ir/serialize.d.ts +1 -1
  194. package/dist/ir/serialize.js +1 -1
  195. package/dist/ir/serialize.js.map +1 -1
  196. package/dist/ir/validate.js +242 -18
  197. package/dist/ir/validate.js.map +1 -1
  198. package/dist/library/int-infer.test.js +4 -4
  199. package/dist/library/int-infer.test.js.map +1 -1
  200. package/dist/library/library-cache.d.ts +1 -0
  201. package/dist/library/library-cache.js +1 -0
  202. package/dist/library/library-cache.js.map +1 -1
  203. package/dist/library/library-cache.test.js +18 -5
  204. package/dist/library/library-cache.test.js.map +1 -1
  205. package/package.json +17 -17
  206. package/surface-manifest.json +163 -54
@@ -7,14 +7,14 @@ import { builtinModules } from "node:module";
7
7
  import { dirname, resolve } from "node:path";
8
8
  import * as ts from "../ts7/adapter.js";
9
9
  import { PoisonError, dynUndefinedExpr, ladderFenceExpr, nodeThrowExpr, own } from "./lowerer.js";
10
- import { canonicalBuiltinModule, isJsSourceFile, isNodeEsmFile, locOf, requireSpecOf } from "../program.js";
10
+ import { canonicalBuiltinModule, isJsSourceFile, isNodeEsmFile, locOf, npmStaticDepSf7, requireSpecOf, resolveImport } from "../program.js";
11
11
  import { isRelativeSpecifier } from "../workspace-registry.js";
12
12
  import { probeNodeRequireRefusal } from "../npm.js";
13
13
  import { isNpmStaticPackage } from "../npm-static.js";
14
14
  import { trackedReadFile } from "../input-tracker.js";
15
15
  import { requireResolvePathsRuntime, resolveImportMetaRuntime, resolveRequireRuntime } from "../runtime-resolve.js";
16
16
  import { invalidJsonModuleDiag, requiresDynamicImportDiag } from "../../diagnostics/diagnostic.js";
17
- import { builtinModuleFnOf, FS_READDIR_DOCUMENTED_OPTIONS, FS_WATCH_DOCUMENTED_OPTIONS, FS_WRITE_FILE_DOCUMENTED_OPTIONS, QS_PARSE_DOCUMENTED_OPTIONS, QS_STRINGIFY_DOCUMENTED_OPTIONS, READLINE_DOCUMENTED_OPTIONS, builtinConstLit, fenceOrDropOptionKey, isChildSurfaceMember, } from "./surfaces.js";
17
+ import { builtinModuleFnOf, FS_READDIR_DOCUMENTED_OPTIONS, FS_WATCH_DOCUMENTED_OPTIONS, FS_WRITE_FILE_DOCUMENTED_OPTIONS, QS_PARSE_DOCUMENTED_OPTIONS, QS_STRINGIFY_DOCUMENTED_OPTIONS, READLINE_DOCUMENTED_OPTIONS, builtinConstLit, fenceOrDropOptionKey, isChildSurfaceMember, NODE_BUILTIN_MODULES_V24, } from "./surfaces.js";
18
18
  import { lowerAbsenceProbe } from "./lower-exprs.js";
19
19
  import { conditionalSpreadOf, lowerDynObjectLiteral } from "./expressions/object-literals.js";
20
20
  import { isSafeToDiscard } from "./expressions/evaluation-safety.js";
@@ -24,8 +24,11 @@ import { CRYPTO_CIPHERS, CRYPTO_CONSTANTS, CRYPTO_CURVES, CRYPTO_HASHES } from "
24
24
  import { generatorMeta, timerStyleCallback } from "./lower-calls.js";
25
25
  import { registerHttpClientFnBinding, voidizedCallback } from "./lower-server.js";
26
26
  import { pairsSnapshotHelper } from "./pairs-snapshot.js";
27
- import { BOOL, BYTES_U8, CHILD_T, CHILDSTREAM_T, DYN, F64, FILEHANDLE_T, FSWATCHER_T, PROCSTREAM_T, JSVAL, NULL_T, SEARCH_PARAMS_T, SPAWNRES_T, STRING, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, funcOf, isUnitType, typeEquals, typeKey } from "../../ir/ir.js";
27
+ import { bufEncoding } from "./containers/bytes.js";
28
+ import { BOOL, BYTES_U8, CHILD_T, CHILDSTREAM_T, CHILDWRITER_T, CRYPTOHASH_T, CRYPTOHMAC_T, DYN, F64, FILEHANDLE_T, FSWATCHER_T, PROCSTREAM_T, JSVAL, NULL_T, SEARCH_PARAMS_T, SPAWNRES_T, STRING, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, funcOf, isUnitType, typeEquals, typeKey } from "../../ir/ir.js";
28
29
  import { boolLit, countedFor, numLit, strLit, varRef } from "../../ir/build.js";
30
+ import { staticForkModulePath } from "../fork-target.js";
31
+ import { tsgoPath } from "../dts-paths.js";
29
32
  function optionalStringTags(lowerer, type) {
30
33
  if (type.kind !== "union")
31
34
  return null;
@@ -439,6 +442,108 @@ export function createRequireNamespaceDecl(lowerer, nameNode, init) {
439
442
  const cr = createRequireSpecOf(lowerer, call);
440
443
  return cr !== null && cr.spec !== null && canonicalBuiltinModule(cr.spec) !== null;
441
444
  }
445
+ /** A statically compiled program module reached through a canonical
446
+ * createRequire binding. This is the ESM twin of an ordinary CommonJS
447
+ * require edge: the same project resolver, require-condition npm-static
448
+ * entry, module initializer, and export registrations own the target. */
449
+ export function createRequireProgramModuleOf(lowerer, expr) {
450
+ if (expr === undefined)
451
+ return null;
452
+ const call = stripTypeCasts(expr);
453
+ if (!ts.isCallExpression(call))
454
+ return null;
455
+ const cr = createRequireSpecOf(lowerer, call);
456
+ if (cr === null || cr.spec === null || canonicalBuiltinModule(cr.spec) !== null)
457
+ return null;
458
+ const dep = resolveImport(lowerer.program, cr.baseFile, cr.spec) ??
459
+ npmStaticDepSf7(lowerer.program, cr.baseFile, cr.spec);
460
+ if (dep === null || dep.fileName.endsWith(".json"))
461
+ return null;
462
+ return { spec: cr.spec, baseFile: cr.baseFile, dep };
463
+ }
464
+ /** True for a const identifier whose initializer is a createRequire call
465
+ * reaching a compiled program module. The binding is namespace/value
466
+ * alias plumbing; its declaration emits only the dependency's run-once
467
+ * initializer at the source position. */
468
+ export function createRequireProgramModuleDecl(lowerer, nameNode, init) {
469
+ if (createRequireProgramModuleOf(lowerer, init) === null)
470
+ return false;
471
+ if (ts.isIdentifier(nameNode))
472
+ return true;
473
+ if (!ts.isObjectBindingPattern(nameNode))
474
+ return false;
475
+ return nameNode.elements.every((element) => element.name !== undefined && element.dotDotDotToken === undefined && element.initializer === undefined &&
476
+ ts.isIdentifier(element.name) &&
477
+ (element.propertyName === undefined || ts.isIdentifier(element.propertyName) || ts.isStringLiteralLike(element.propertyName)));
478
+ }
479
+ /** node:module's two compiler-only calls. isBuiltin compares one evaluated
480
+ * string against the pinned Node 24 list (bare builtins also accept their
481
+ * node: spelling; prefix-only entries do not gain a bare alias).
482
+ * syncBuiltinESMExports is observably a no-op inside the static surface:
483
+ * builtin exports cannot be mutated, so its only supported behavior is
484
+ * evaluating no arguments and returning undefined. */
485
+ export function lowerNodeModuleCall(lowerer, expr, bi, loc) {
486
+ if (bi.module !== "module")
487
+ return null;
488
+ if (bi.member === "syncBuiltinESMExports") {
489
+ if (expr.arguments.length !== 0 || expr.arguments.some(ts.isSpreadElement)) {
490
+ lowerer.noLowering(`module.syncBuiltinESMExports with ${expr.arguments.length} arguments`, expr, "syncBuiltinESMExports() takes no arguments");
491
+ }
492
+ if (!ts.isExpressionStatement(expr.parent)) {
493
+ lowerer.noLowering("module.syncBuiltinESMExports used as a value", expr, "call syncBuiltinESMExports() as its own statement; the supported static effect is a no-op");
494
+ }
495
+ return { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc };
496
+ }
497
+ if (bi.member !== "isBuiltin")
498
+ return null;
499
+ if (expr.arguments.length !== 1 || expr.arguments.some(ts.isSpreadElement)) {
500
+ lowerer.noLowering(`module.isBuiltin with ${expr.arguments.length} arguments`, expr, "isBuiltin(moduleName) takes one string");
501
+ }
502
+ const argumentNode = expr.arguments[0];
503
+ const argument = lowerer.lowerExpr(argumentNode);
504
+ if (argument.type.kind !== "string") {
505
+ if (argument.type.kind === "dyn" || argument.type.kind === "jsval" || argument.type.kind === "union") {
506
+ lowerer.noLowering("module.isBuiltin with a runtime-polymorphic argument", argumentNode, "narrow the module name to a string first; statically non-string values return false");
507
+ }
508
+ return {
509
+ kind: "seqExpr",
510
+ stmts: [{ kind: "exprStmt", expr: argument, loc: locOf(argumentNode) }],
511
+ result: boolLit(false, loc),
512
+ type: BOOL,
513
+ loc,
514
+ };
515
+ }
516
+ const slot = lowerer.declareHiddenLocal("%builtinName", STRING);
517
+ const ref = () => varRef(slot.id, STRING, loc);
518
+ const accepted = NODE_BUILTIN_MODULES_V24.flatMap((name) => name.startsWith("node:") ? [name] : [name, `node:${name}`]);
519
+ let staticArgument = argumentNode;
520
+ while (ts.isParenthesizedExpression(staticArgument) || ts.isAsExpression(staticArgument) ||
521
+ ts.isTypeAssertion(staticArgument) || ts.isNonNullExpression(staticArgument)) {
522
+ staticArgument = staticArgument.expression;
523
+ }
524
+ if (ts.isStringLiteralLike(staticArgument)) {
525
+ return boolLit(accepted.includes(staticArgument.text), loc);
526
+ }
527
+ let result = boolLit(false, loc);
528
+ for (let i = accepted.length - 1; i >= 0; i--) {
529
+ const equal = {
530
+ kind: "strEq",
531
+ negated: false,
532
+ left: ref(),
533
+ right: strLit(accepted[i], loc),
534
+ type: BOOL,
535
+ loc,
536
+ };
537
+ result = { kind: "logical", op: "||", left: equal, right: result, type: BOOL, loc };
538
+ }
539
+ return {
540
+ kind: "seqExpr",
541
+ stmts: [{ kind: "varDecl", localId: slot.id, init: argument, loc }],
542
+ result,
543
+ type: BOOL,
544
+ loc,
545
+ };
546
+ }
442
547
  /** `require("spec")` through a createRequire binding — the erasure per
443
548
  * target. Builtins are reached here only OUTSIDE the const-namespace-
444
549
  * binding shape (that declaration erases; member uses resolve through
@@ -465,12 +570,16 @@ export function lowerCreateRequireCall(lowerer, call, loc) {
465
570
  if (canonicalBuiltinModule(spec) !== null) {
466
571
  lowerer.unsupported("SC1090", call, `module namespace objects as values (bind it first: const m = require("${spec}"), then access members through the binding)`);
467
572
  }
573
+ const programModule = createRequireProgramModuleOf(lowerer, call);
574
+ if (programModule !== null) {
575
+ lowerer.noLowering(`createRequire's module namespace value for '${spec}'`, call, `bind it once (const m = require(${JSON.stringify(spec)})) and access statically-known members through that binding`);
576
+ }
468
577
  if (spec.startsWith("#")) {
469
578
  lowerer.noLowering(`createRequire's require of the '${spec}' project import`, call, "imports-field specifiers have no require lowering yet — import the target statically");
470
579
  }
471
580
  if (isRelativeSpecifier(spec) || spec.startsWith("/")) {
472
581
  if (!spec.endsWith(".json")) {
473
- lowerer.noLowering(`createRequire's require of '${spec}'`, call, "relative requires lower for .json documents only — a program module is a static import");
582
+ lowerer.noLowering(`createRequire's require of '${spec}'`, call, "relative program modules lower when they resolve into the compiled graph; relative .json documents bake at build time");
474
583
  }
475
584
  const abs = spec.startsWith("/")
476
585
  ? spec
@@ -891,6 +1000,86 @@ function optionMember(p) {
891
1000
  }
892
1001
  return null;
893
1002
  }
1003
+ /** Normalize fs.readSync/fs.writeSync's shorthand and inline-options buffer
1004
+ * forms into the existing fixed-width descriptor IR. fd and buffer bind
1005
+ * first, option values bind in object-literal order, and only then do the
1006
+ * default window expressions read the bound buffer length. */
1007
+ function lowerFsSyncBufferWindow(lowerer, expr, loc, fn, source, supported) {
1008
+ const fdValue = lowerer.lowerExprExpecting(expr.arguments[0], F64);
1009
+ const bufferValue = lowerer.lowerExprExpecting(expr.arguments[1], BYTES_U8);
1010
+ const fdLocal = lowerer.declareHiddenLocal("%fsSyncFd", F64);
1011
+ const bufferLocal = lowerer.declareHiddenLocal("%fsSyncBuffer", BYTES_U8);
1012
+ const stmts = [
1013
+ { kind: "varDecl", localId: fdLocal.id, init: fdValue, loc: fdValue.loc },
1014
+ { kind: "varDecl", localId: bufferLocal.id, init: bufferValue, loc: bufferValue.loc },
1015
+ ];
1016
+ const fd = () => varRef(fdLocal.id, fdLocal.type, loc);
1017
+ const buffer = () => varRef(bufferLocal.id, bufferLocal.type, loc);
1018
+ const number = (value) => numLit(value, loc);
1019
+ const stageNumber = (name, node) => {
1020
+ const mapped = lowerer.mapTypeOf(lowerer.typeOf(node));
1021
+ if (mapped?.kind !== "f64") {
1022
+ lowerer.noLowering(`${fn} with a '${mapped ? lowerer.fmt(mapped) : lowerer.checker.typeToString(lowerer.typeOf(node))}' ${name}`, node, supported);
1023
+ }
1024
+ const value = lowerer.lowerExprExpecting(node, F64);
1025
+ const local = lowerer.declareHiddenLocal(`%fsSync${name}`, F64);
1026
+ stmts.push({ kind: "varDecl", localId: local.id, init: value, loc: value.loc });
1027
+ return varRef(local.id, local.type, loc);
1028
+ };
1029
+ const isNull = (node) => {
1030
+ let value = node;
1031
+ while (ts.isParenthesizedExpression(value))
1032
+ value = value.expression;
1033
+ return value.kind === ts.SyntaxKind.NullKeyword;
1034
+ };
1035
+ let offset = null;
1036
+ let length = null;
1037
+ let position = null;
1038
+ if (source.kind === "offset") {
1039
+ offset = stageNumber("offset", source.node);
1040
+ }
1041
+ else if (source.node !== undefined && !isNull(source.node)) {
1042
+ if (!ts.isObjectLiteralExpression(source.node)) {
1043
+ lowerer.noLowering(`${fn} with a non-literal options argument`, source.node, supported);
1044
+ }
1045
+ for (const property of source.node.properties) {
1046
+ const member = optionMember(property);
1047
+ if (member === null) {
1048
+ lowerer.noLowering(`${fn} with a non-plain options member`, property, supported);
1049
+ }
1050
+ if (member.name === "offset") {
1051
+ offset = stageNumber("offset", member.value);
1052
+ }
1053
+ else if (member.name === "length") {
1054
+ length = stageNumber("length", member.value);
1055
+ }
1056
+ else if (member.name === "position") {
1057
+ position = isNull(member.value) ? null : stageNumber("position", member.value);
1058
+ }
1059
+ else {
1060
+ const effect = lowerer.lowerExpr(member.value);
1061
+ stmts.push({ kind: "exprStmt", expr: effect, loc: effect.loc });
1062
+ }
1063
+ }
1064
+ }
1065
+ const normalizedOffset = offset ?? number(0);
1066
+ const normalizedLength = length ?? {
1067
+ kind: "bin",
1068
+ op: "-",
1069
+ left: { kind: "bytesIntrinsic", method: "length", receiver: buffer(), args: [], type: F64, loc },
1070
+ right: normalizedOffset,
1071
+ type: F64,
1072
+ loc,
1073
+ };
1074
+ const result = {
1075
+ kind: "libCall",
1076
+ fn,
1077
+ args: [fd(), buffer(), normalizedOffset, normalizedLength, position ?? number(-1)],
1078
+ type: F64,
1079
+ loc,
1080
+ };
1081
+ return { kind: "seqExpr", stmts, result, type: F64, loc };
1082
+ }
894
1083
  /** One builtin-module function call → its libCall. Completes the call to
895
1084
  * the table's exact shape: variadicPack functions (path.join/resolve)
896
1085
  * accept any arity — or ONE spread of a string[] — and pack the
@@ -1061,14 +1250,152 @@ export function lowerFsLadderCall(lowerer, expr, bi, loc) {
1061
1250
  return null;
1062
1251
  }
1063
1252
  }
1253
+ const ZLIB_SYNC_FNS = {
1254
+ deflateSync: "zlib.deflateSync",
1255
+ inflateSync: "zlib.inflateSync",
1256
+ deflateRawSync: "zlib.deflateRawSync",
1257
+ inflateRawSync: "zlib.inflateRawSync",
1258
+ gzipSync: "zlib.gzipSync",
1259
+ gunzipSync: "zlib.gunzipSync",
1260
+ unzipSync: "zlib.unzipSync",
1261
+ };
1262
+ const ZLIB_CALLBACK_FNS = {
1263
+ deflate: "zlib.deflateCb",
1264
+ inflate: "zlib.inflateCb",
1265
+ deflateRaw: "zlib.deflateRawCb",
1266
+ inflateRaw: "zlib.inflateRawCb",
1267
+ gzip: "zlib.gzipCb",
1268
+ gunzip: "zlib.gunzipCb",
1269
+ unzip: "zlib.unzipCb",
1270
+ };
1271
+ function zlibInputBytes(lowerer, node, loc) {
1272
+ const value = lowerer.lowerExpr(node);
1273
+ if (value.type.kind === "bytes" && value.type.elem === "u8")
1274
+ return value;
1275
+ if (value.type.kind === "string") {
1276
+ return {
1277
+ kind: "libCall",
1278
+ fn: "buffer.fromStr",
1279
+ args: [value, { kind: "strLit", value: "utf8", type: STRING, loc }],
1280
+ type: BYTES_U8,
1281
+ loc,
1282
+ };
1283
+ }
1284
+ lowerer.noLowering(`zlib byte input of '${lowerer.fmt(value.type)}' values`, node, "string and Buffer/Uint8Array values are supported");
1285
+ }
1286
+ function lowerZlibModuleCall(lowerer, expr, bi, loc) {
1287
+ if (expr.arguments.some(ts.isSpreadElement)) {
1288
+ lowerer.noLowering(`zlib.${bi.member} with spread arguments`, expr);
1289
+ }
1290
+ const syncFn = ZLIB_SYNC_FNS[bi.member];
1291
+ if (syncFn !== undefined) {
1292
+ if (expr.arguments.length !== 1) {
1293
+ const site = expr.arguments[1] ?? expr;
1294
+ lowerer.noLowering(`${bi.member} with explicit options`, site, `${bi.member}(data) with Node's default options is supported`);
1295
+ }
1296
+ return {
1297
+ kind: "libCall",
1298
+ fn: syncFn,
1299
+ args: [zlibInputBytes(lowerer, expr.arguments[0], loc)],
1300
+ type: BYTES_U8,
1301
+ loc,
1302
+ };
1303
+ }
1304
+ const callbackFn = ZLIB_CALLBACK_FNS[bi.member];
1305
+ if (callbackFn !== undefined) {
1306
+ if (expr.arguments.length !== 2) {
1307
+ const site = expr.arguments.length >= 3 ? expr.arguments[1] : expr;
1308
+ lowerer.noLowering(`${bi.member} with ${expr.arguments.length} arguments`, site, `${bi.member}(data, callback) with Node's default options is supported; explicit options are not yet lowered`);
1309
+ }
1310
+ return {
1311
+ kind: "libCall",
1312
+ fn: callbackFn,
1313
+ args: [
1314
+ zlibInputBytes(lowerer, expr.arguments[0], loc),
1315
+ errorFirstBytesCallback(lowerer, expr.arguments[1], "zlib"),
1316
+ ],
1317
+ type: VOID,
1318
+ loc,
1319
+ };
1320
+ }
1321
+ if (bi.member === "crc32") {
1322
+ if (expr.arguments.length < 1 || expr.arguments.length > 2) {
1323
+ lowerer.noLowering(`zlib.crc32 with ${expr.arguments.length} arguments`, expr, "crc32(data[, initialValue]) is supported");
1324
+ }
1325
+ return {
1326
+ kind: "libCall",
1327
+ fn: "zlib.crc32",
1328
+ args: [
1329
+ zlibInputBytes(lowerer, expr.arguments[0], loc),
1330
+ expr.arguments[1]
1331
+ ? lowerer.lowerExprExpecting(expr.arguments[1], F64)
1332
+ : { kind: "numLit", value: 0, type: F64, loc },
1333
+ ],
1334
+ type: F64,
1335
+ loc,
1336
+ };
1337
+ }
1338
+ throw new InternalCompilerError(`unhandled lowered zlib member ${bi.member}`);
1339
+ }
1064
1340
  export function lowerBuiltinModuleCall(lowerer, expr, bi, fn, loc) {
1065
1341
  const name = expr.expression.getText();
1342
+ // Numeric open flags are interpreted symbolically at the call site. The
1343
+ // O_* bit values differ between Darwin and Linux, so emit a stable mask
1344
+ // and let the target runtime select its own native constants.
1345
+ if (bi.module === "fs" && bi.member === "openSync" && expr.arguments.length >= 2 &&
1346
+ lowerer.mapTypeOf(lowerer.typeOf(expr.arguments[1]))?.kind === "f64") {
1347
+ if (expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement)) {
1348
+ lowerer.noLowering("openSync with numeric flags and this argument shape", expr);
1349
+ }
1350
+ const bits = {
1351
+ O_RDONLY: 0, O_WRONLY: 1, O_RDWR: 2, O_CREAT: 4, O_EXCL: 8,
1352
+ O_NOFOLLOW: 16, O_NONBLOCK: 32, O_TRUNC: 64, O_APPEND: 128,
1353
+ };
1354
+ const flagsOf = (input) => {
1355
+ let node = input;
1356
+ while (ts.isParenthesizedExpression(node) || ts.isAsExpression(node))
1357
+ node = node.expression;
1358
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.BarToken) {
1359
+ const left = flagsOf(node.left);
1360
+ const right = flagsOf(node.right);
1361
+ return left === null || right === null ? null : left | right;
1362
+ }
1363
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
1364
+ const imported = lowerer.builtinImportOf(node.expression);
1365
+ if (imported?.module === "fs" && imported.member === "constants") {
1366
+ return bits[node.name.text] ?? null;
1367
+ }
1368
+ }
1369
+ if (ts.isPropertyAccessExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
1370
+ const imported = lowerer.builtinMemberOf(node.expression);
1371
+ if (imported?.module === "fs" && imported.member === "constants") {
1372
+ return bits[node.name.text] ?? null;
1373
+ }
1374
+ }
1375
+ return null;
1376
+ };
1377
+ const mask = flagsOf(expr.arguments[1]);
1378
+ if (mask === null) {
1379
+ lowerer.noLowering("openSync with computed numeric flags", expr.arguments[1], "use an inline bitwise OR of fs.constants.O_RDONLY/O_WRONLY/O_RDWR/O_CREAT/O_EXCL/O_NOFOLLOW/O_NONBLOCK/O_TRUNC/O_APPEND");
1380
+ }
1381
+ const path = lowerer.lowerExprExpecting(expr.arguments[0], STRING);
1382
+ const mode = expr.arguments[2]
1383
+ ? lowerer.lowerExprExpecting(expr.arguments[2], F64)
1384
+ : { kind: "numLit", value: 0o666, type: F64, loc };
1385
+ return { kind: "libCall", fn: "fs.openNumericSync", args: [path,
1386
+ { kind: "numLit", value: mask, type: F64, loc }, mode], type: F64, loc };
1387
+ }
1388
+ if (bi.module === "zlib")
1389
+ return lowerZlibModuleCall(lowerer, expr, bi, loc);
1066
1390
  if (bi.module === "child_process" && bi.member === "spawnSync") {
1067
1391
  return lowerer.lowerSpawnSyncCall(expr, loc);
1068
1392
  }
1069
1393
  if (bi.module === "child_process" && bi.member === "spawn") {
1070
1394
  return lowerer.lowerSpawnCall(expr, loc);
1071
1395
  }
1396
+ if (bi.module === "child_process" && bi.member === "fork") {
1397
+ return lowerForkCall(lowerer, expr, loc);
1398
+ }
1072
1399
  if (bi.module === "fs" && bi.member === "watch") {
1073
1400
  return lowerFsWatchCall(lowerer, expr, loc);
1074
1401
  }
@@ -1148,18 +1475,29 @@ export function lowerBuiltinModuleCall(lowerer, expr, bi, fn, loc) {
1148
1475
  callback = voidizedCallback(lowerer, callback, locOf(expr.arguments[2]));
1149
1476
  return { kind: "libCall", fn: "fs.renameCb", args: [oldPath, newPath, callback], type: VOID, loc };
1150
1477
  }
1151
- // fs.readSync(fd, buffer, offset, length[, position]) — normalize the
1152
- // current-offset forms to Node/libuv's -1 sentinel so the IR and native
1153
- // ABI stay fixed-width. A real numeric position is a positioned read
1154
- // and therefore must not advance the descriptor. Literal null is the
1155
- // documented current-offset spelling; richer nullable expressions must
1156
- // narrow first so no effectful evaluation is silently discarded.
1478
+ // fs.readSync's classic and options-object buffer forms normalize into
1479
+ // one fixed-width IR call. Current-offset forms use Node/libuv's -1
1480
+ // sentinel; a numeric position performs offset-preserving I/O.
1157
1481
  if (bi.module === "fs" && bi.member === "readSync") {
1158
- const supported = "use readSync(fd, buffer, offset, length[, position]) with a numeric position or literal null; options objects and bigint positions have no lowering";
1159
- if ((expr.arguments.length !== 4 && expr.arguments.length !== 5) ||
1482
+ const supported = "use readSync(fd, buffer), readSync(fd, buffer, { offset?, length?, position? }), or readSync(fd, buffer, offset, length[, position]); bigint positions and non-literal options objects have no lowering";
1483
+ if (expr.arguments.length < 2 ||
1484
+ expr.arguments.length > 5 ||
1160
1485
  expr.arguments.some(ts.isSpreadElement)) {
1161
1486
  lowerer.noLowering(`readSync with ${expr.arguments.length} argument${expr.arguments.length === 1 ? "" : "s"}`, expr, supported);
1162
1487
  }
1488
+ if (expr.arguments.length === 2) {
1489
+ return lowerFsSyncBufferWindow(lowerer, expr, loc, "fs.readSync", { kind: "options", node: undefined }, supported);
1490
+ }
1491
+ if (expr.arguments.length === 3) {
1492
+ const optionsNode = expr.arguments[2];
1493
+ let valueNode = optionsNode;
1494
+ while (ts.isParenthesizedExpression(valueNode))
1495
+ valueNode = valueNode.expression;
1496
+ if (ts.isObjectLiteralExpression(valueNode) || valueNode.kind === ts.SyntaxKind.NullKeyword) {
1497
+ return lowerFsSyncBufferWindow(lowerer, expr, loc, "fs.readSync", { kind: "options", node: valueNode }, supported);
1498
+ }
1499
+ lowerer.noLowering("readSync with a non-literal options argument", optionsNode, supported);
1500
+ }
1163
1501
  const args = [
1164
1502
  lowerer.lowerExprExpecting(expr.arguments[0], F64),
1165
1503
  lowerer.lowerExprExpecting(expr.arguments[1], BYTES_U8),
@@ -1183,20 +1521,18 @@ export function lowerBuiltinModuleCall(lowerer, expr, bi, fn, loc) {
1183
1521
  }
1184
1522
  return { kind: "libCall", fn: "fs.readSync", args, type: F64, loc };
1185
1523
  }
1186
- // fs.writeSync has two static families. Buffer writes use the classic
1187
- // (fd, buffer, offset, length[, position]) shape; string writes accept
1188
- // (fd, string[, position[, "utf8"]]). The runtime uses -1 for the
1189
- // current-offset forms. Node treats a negative, fractional, non-finite,
1190
- // or over-MAX_SAFE numeric write position like null rather than throwing;
1191
- // preserve the value here so the runtime can make that dispatch.
1524
+ // fs.writeSync has buffer and string families. Buffer writes accept
1525
+ // omitted offset/length and inline options; string writes accept
1526
+ // (fd, string[, position[, "utf8"]]). The runtime uses -1 for current
1527
+ // offset. Invalid numeric WRITE positions intentionally reach the
1528
+ // runtime, where Node normalizes them to the current descriptor offset.
1192
1529
  if (bi.module === "fs" && bi.member === "writeSync") {
1193
- const supported = 'use writeSync(fd, buffer, offset, length[, position]) or writeSync(fd, string[, position[, "utf8"]]); options objects, bigint positions, and other encodings have no lowering';
1530
+ const supported = 'use writeSync(fd, buffer[, offset[, length[, position]]]), writeSync(fd, buffer, { offset?, length?, position? }), or writeSync(fd, string[, position[, "utf8"]]); non-literal options objects, bigint positions, and other encodings have no lowering';
1194
1531
  if (expr.arguments.some(ts.isSpreadElement) || expr.arguments.length < 2) {
1195
1532
  lowerer.noLowering(`writeSync with ${expr.arguments.length} argument${expr.arguments.length === 1 ? "" : "s"}`, expr, supported);
1196
1533
  }
1197
1534
  const dataNode = expr.arguments[1];
1198
1535
  const dataType = lowerer.mapTypeOf(lowerer.typeOf(dataNode));
1199
- const fd = lowerer.lowerExprExpecting(expr.arguments[0], F64);
1200
1536
  const position = (node) => {
1201
1537
  let valueNode = node;
1202
1538
  while (valueNode && ts.isParenthesizedExpression(valueNode))
@@ -1214,9 +1550,26 @@ export function lowerBuiltinModuleCall(lowerer, expr, bi, fn, loc) {
1214
1550
  if (dataType.elem !== "u8") {
1215
1551
  lowerer.noLowering(`writeSync of '${lowerer.fmt(dataType)}' data`, dataNode, "byte writes take Uint8Array/Buffer data");
1216
1552
  }
1553
+ if (expr.arguments.length === 2) {
1554
+ return lowerFsSyncBufferWindow(lowerer, expr, loc, "fs.writeSync", { kind: "options", node: undefined }, supported);
1555
+ }
1556
+ if (expr.arguments.length === 3) {
1557
+ const thirdNode = expr.arguments[2];
1558
+ let valueNode = thirdNode;
1559
+ while (ts.isParenthesizedExpression(valueNode))
1560
+ valueNode = valueNode.expression;
1561
+ if (ts.isObjectLiteralExpression(valueNode) || valueNode.kind === ts.SyntaxKind.NullKeyword) {
1562
+ return lowerFsSyncBufferWindow(lowerer, expr, loc, "fs.writeSync", { kind: "options", node: valueNode }, supported);
1563
+ }
1564
+ if (lowerer.mapTypeOf(lowerer.typeOf(thirdNode))?.kind === "f64") {
1565
+ return lowerFsSyncBufferWindow(lowerer, expr, loc, "fs.writeSync", { kind: "offset", node: thirdNode }, supported);
1566
+ }
1567
+ lowerer.noLowering("writeSync of Buffer data with a non-numeric offset", thirdNode, supported);
1568
+ }
1217
1569
  if (expr.arguments.length !== 4 && expr.arguments.length !== 5) {
1218
1570
  lowerer.noLowering(`writeSync of Buffer data with ${expr.arguments.length} arguments`, expr, supported);
1219
1571
  }
1572
+ const fd = lowerer.lowerExprExpecting(expr.arguments[0], F64);
1220
1573
  return {
1221
1574
  kind: "libCall",
1222
1575
  fn: "fs.writeSync",
@@ -1247,6 +1600,7 @@ export function lowerBuiltinModuleCall(lowerer, expr, bi, fn, loc) {
1247
1600
  // a call/getter whose type is the accepted literal cannot vanish.
1248
1601
  enc = lowerer.lowerExprExpecting(encNode, STRING);
1249
1602
  }
1603
+ const fd = lowerer.lowerExprExpecting(expr.arguments[0], F64);
1250
1604
  return {
1251
1605
  kind: "libCall",
1252
1606
  fn: "fs.writeStrSync",
@@ -1264,14 +1618,17 @@ export function lowerBuiltinModuleCall(lowerer, expr, bi, fn, loc) {
1264
1618
  if (bi.module === "os" && bi.member === "networkInterfaces") {
1265
1619
  return lowerOsNetworkInterfacesCall(lowerer, expr, loc);
1266
1620
  }
1267
- // fs.readdirSync(path, { withFileTypes: true }) — the Dirent form:
1268
- // routed BEFORE the 1-arg table completion. The options must be an
1269
- // object literal with withFileTypes: true (encoding "utf8"/"utf-8" is
1270
- // accepted as the default it is; recursive and encoding:'buffer'
1271
- // fence), and the call site's mapped type must be the interned Dirent
1272
- // record array (type-mapper.ts) — the userInfo verification stance.
1273
- if (bi.module === "fs" && bi.member === "readdirSync" && expr.arguments.length === 2) {
1274
- return lowerFsReaddirTypesCall(lowerer, expr, loc);
1621
+ // fs.readdirSync/fs.promises.readdir(path, { withFileTypes: true }) —
1622
+ // the Dirent forms, routed BEFORE the 1-arg table completion. The
1623
+ // options must be an object literal with withFileTypes: true (encoding
1624
+ // "utf8"/"utf-8" is accepted as the default it is; recursive and
1625
+ // encoding:'buffer' fence), and the call site's mapped type must carry
1626
+ // the interned Dirent record array (type-mapper.ts) — the userInfo
1627
+ // verification stance.
1628
+ const syncReaddirTypes = bi.module === "fs" && bi.member === "readdirSync";
1629
+ const promiseReaddirTypes = bi.module === "fs/promises" && bi.member === "readdir";
1630
+ if ((syncReaddirTypes || promiseReaddirTypes) && expr.arguments.length === 2) {
1631
+ return lowerFsReaddirTypesCall(lowerer, expr, loc, promiseReaddirTypes);
1275
1632
  }
1276
1633
  if (bi.module === "os" && bi.member === "userInfo") {
1277
1634
  return lowerOsUserInfoCall(lowerer, expr, loc);
@@ -1717,14 +2074,6 @@ export function lowerBuiltinModuleCall(lowerer, expr, bi, fn, loc) {
1717
2074
  }
1718
2075
  return finishWrite(optionValues);
1719
2076
  }
1720
- // zlib takes Buffers; a string argument (the lib admits it) gets the
1721
- // wrap-it-first hint instead of a generic type mismatch.
1722
- if (bi.module === "zlib" && expr.arguments.length >= 1) {
1723
- const dataIr = lowerer.mapTypeOf(lowerer.typeOf(expr.arguments[0]));
1724
- if (!(dataIr?.kind === "bytes" && dataIr.elem === "u8")) {
1725
- lowerer.noLowering(`${bi.member} of '${dataIr ? lowerer.fmt(dataIr) : lowerer.checker.typeToString(lowerer.typeOf(expr.arguments[0]))}' data`, expr.arguments[0], `zlib works on Buffers: ${bi.member}(Buffer.from(s, "utf8"))`);
1726
- }
1727
- }
1728
2077
  if (fn.variadicPack) {
1729
2078
  // join(...parts) forwards the array itself; mixing spread and plain
1730
2079
  // arguments (or spreading anything but a string[]) stays out.
@@ -1784,12 +2133,21 @@ export function lowerBuiltinModuleCall(lowerer, expr, bi, fn, loc) {
1784
2133
  }
1785
2134
  }
1786
2135
  const required = fn.params.length - (fn.defaults?.length ?? 0);
1787
- if (expr.arguments.length < required || expr.arguments.length > fn.params.length) {
2136
+ const hasSpread = expr.arguments.some(ts.isSpreadElement);
2137
+ if (hasSpread && bi.module === "url" && bi.member === "fileURLToPath") {
2138
+ lowerer.noLowering("fileURLToPath with spread arguments", expr, "call fileURLToPath(value) directly so the URL-or-string overload remains statically visible");
2139
+ }
2140
+ if (hasSpread &&
2141
+ ((bi.module === "fs" && bi.member === "readFileSync") ||
2142
+ (bi.module === "fs/promises" && bi.member === "readFile"))) {
2143
+ lowerer.noLowering(`${bi.member} with spread arguments`, expr, `call ${bi.member}(path, "utf8") directly so the required literal encoding remains statically visible`);
2144
+ }
2145
+ if (!hasSpread && (expr.arguments.length < required || expr.arguments.length > fn.params.length)) {
1788
2146
  lowerer.noLowering(`${name} with ${expr.arguments.length} argument${expr.arguments.length === 1 ? "" : "s"}`, expr, bi.member === "readFileSync" || bi.member === "readFile"
1789
2147
  ? `pass the encoding: ${bi.member}(path, "utf8") — Buffer reads and options objects have no lowering`
1790
2148
  : `the supported form takes ${fn.params.length} argument${fn.params.length === 1 ? "" : "s"} (no options objects)`);
1791
2149
  }
1792
- if (bi.module === "url" && bi.member === "fileURLToPath") {
2150
+ if (!hasSpread && bi.module === "url" && bi.member === "fileURLToPath") {
1793
2151
  // Node accepts a URL value or a URL string — one libFn per receiver
1794
2152
  // form, picked by the argument's static type. Unions (URL |
1795
2153
  // undefined, ...) must narrow first, like everywhere else.
@@ -1803,8 +2161,8 @@ export function lowerBuiltinModuleCall(lowerer, expr, bi, fn, loc) {
1803
2161
  }
1804
2162
  lowerer.noLowering(`fileURLToPath of '${lowerer.fmt(arg.type)}' values`, argNode, "pass a URL value or a URL string (narrow unions first)");
1805
2163
  }
1806
- if ((bi.module === "fs" && bi.member === "readFileSync") ||
1807
- (bi.module === "fs/promises" && bi.member === "readFile")) {
2164
+ if (!hasSpread && ((bi.module === "fs" && bi.member === "readFileSync") ||
2165
+ (bi.module === "fs/promises" && bi.member === "readFile"))) {
1808
2166
  // The runtime reads utf8 unconditionally; any other encoding would
1809
2167
  // silently decode wrong, so the ARGUMENT'S TYPE must be the literal
1810
2168
  // "utf8" — or Node's "utf-8" alias, the same decoder (the fallback
@@ -1815,10 +2173,48 @@ export function lowerBuiltinModuleCall(lowerer, expr, bi, fn, loc) {
1815
2173
  lowerer.noLowering(`${bi.member} with a non-"utf8" encoding`, expr.arguments[1], `only utf8 reads are supported: ${bi.member}(path, "utf8")`);
1816
2174
  }
1817
2175
  }
1818
- const args = expr.arguments.map((a, i) => lowerer.lowerExprExpecting(a, fn.params[i]));
1819
- for (let i = args.length; i < fn.params.length; i++) {
1820
- const dflt = fn.defaults[i - required];
1821
- args.push({ kind: "strLit", value: dflt, type: STRING, loc });
2176
+ // A value-enabled builtin with defaults uses its generated adapter for
2177
+ // direct calls too. That keeps explicit undefined identical to omission:
2178
+ // completeArgs wraps the undefined arm, then the adapter selects the
2179
+ // descriptor's default before entering the fixed runtime libCall ABI.
2180
+ // The call has already passed this member's ordinary arity/shape gates.
2181
+ if (fn.defaults && fn.valueParams) {
2182
+ const callee = lowerer.lowerBuiltinCallableValue(bi, loc);
2183
+ if (!callee || callee.type.kind !== "func") {
2184
+ throw new InternalCompilerError(`missing callable adapter for ${bi.module}.${bi.member}`);
2185
+ }
2186
+ const funcType = callee.type;
2187
+ const shapes = fn.valueParams.map((param, index) => {
2188
+ const type = funcType.params[index];
2189
+ if (!type)
2190
+ throw new InternalCompilerError(`missing callable parameter ${index} for ${bi.module}.${bi.member}`);
2191
+ return { type, mode: param.mode === "optional" ? "omittable" : "required" };
2192
+ });
2193
+ const args = lowerer.completeArgs(expr.arguments, shapes, loc, expr);
2194
+ return { kind: "callValue", callee, args, type: callee.type.ret, loc };
2195
+ }
2196
+ let args;
2197
+ if (hasSpread) {
2198
+ const shapes = fn.params.map((type, i) => i < required
2199
+ ? { type, mode: "required" }
2200
+ : {
2201
+ type,
2202
+ mode: "omittable",
2203
+ callDefault: {
2204
+ kind: "strLit",
2205
+ value: fn.defaults[i - required],
2206
+ type: STRING,
2207
+ loc,
2208
+ },
2209
+ });
2210
+ args = lowerer.completeArgs(expr.arguments, shapes, loc, expr);
2211
+ }
2212
+ else {
2213
+ args = expr.arguments.map((a, i) => lowerer.lowerExprExpecting(a, fn.params[i]));
2214
+ for (let i = args.length; i < fn.params.length; i++) {
2215
+ const dflt = fn.defaults[i - required];
2216
+ args.push({ kind: "strLit", value: dflt, type: STRING, loc });
2217
+ }
1822
2218
  }
1823
2219
  return { kind: "libCall", fn: fn.fn, args, type: fn.result, loc };
1824
2220
  }
@@ -2028,13 +2424,12 @@ export function lowerSpawnSyncCall(lowerer, expr, loc) {
2028
2424
  };
2029
2425
  }
2030
2426
  /** `spawn(command, args?, options)` → one cp.spawn / cp.spawnOpts
2031
- * libCall. The options argument must be an object LITERAL with an
2032
- * EXPLICIT stdio — "ignore" or "inherit" as the scalar, or the 3-tuple
2033
- * whose stdout/stderr slots may also be "pipe" (the child.stdout/
2034
- * child.stderr streams) or number fds; piped STDIN fences, and
2035
- * OMITTING the options means Node's default stdio, "pipe" on all
2036
- * three — fenced too, so a program never silently loses its child's
2037
- * output. The other lowered
2427
+ * libCall. The options argument, when present, must be an object
2428
+ * LITERAL. Stdio accepts "ignore", "inherit", or "pipe" as the scalar,
2429
+ * or the 3-tuple whose stdin pipe becomes child.stdin, stdout/stderr
2430
+ * pipes become child.stdout/child.stderr, and output slots may also be
2431
+ * number fds. Omitting stdio uses Node's default: all three piped. The
2432
+ * other lowered
2038
2433
  * members: `detached` (a boolean literal, inline or carried by the
2039
2434
  * conditional spread `...(c ? { detached: true } : {})` in either
2040
2435
  * orientation — POSIX_SPAWN_SETSID, the child gets its own session and
@@ -2044,11 +2439,28 @@ export function lowerSpawnSyncCall(lowerer, expr, loc) {
2044
2439
  * keeps its historical cp.spawn lowering. */
2045
2440
  export function lowerSpawnCall(lowerer, expr, loc) {
2046
2441
  if (expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement)) {
2047
- lowerer.noLowering("spawn with this argument shape", expr, 'the supported form is spawn(command, args?, { stdio: "ignore" | "inherit", detached?, env?, cwd? })');
2442
+ lowerer.noLowering("spawn with this argument shape", expr, 'the supported form is spawn(command, args?, { stdio: "pipe" | "ignore" | "inherit", detached?, env?, cwd? })');
2048
2443
  }
2049
2444
  const cmd = lowerer.lowerExprExpecting(expr.arguments[0], STRING);
2050
- const argsNode = expr.arguments.length === 3 ? expr.arguments[1] : undefined;
2051
- const optsNode = expr.arguments[expr.arguments.length - 1];
2445
+ let argsNode;
2446
+ let optsNode;
2447
+ if (expr.arguments.length === 3) {
2448
+ argsNode = expr.arguments[1];
2449
+ optsNode = expr.arguments[2];
2450
+ }
2451
+ else if (expr.arguments.length === 2) {
2452
+ const second = expr.arguments[1];
2453
+ const secondType = lowerer.typeOf(second);
2454
+ const mapped = lowerer.mapTypeOf(secondType);
2455
+ if (lowerer.checker.isArrayType(secondType) || lowerer.checker.isTupleType(secondType) ||
2456
+ (mapped?.kind === "array" && mapped.elem.kind === "string"))
2457
+ argsNode = second;
2458
+ else
2459
+ optsNode = second;
2460
+ }
2461
+ if (optsNode !== undefined && !ts.isObjectLiteralExpression(optsNode)) {
2462
+ lowerer.noLowering("spawn with a non-literal options argument", optsNode, "pass stdio, detached, env, cwd, windowsHide, and shell in an inline object literal");
2463
+ }
2052
2464
  const emptyStr = { kind: "strLit", value: "", type: STRING, loc };
2053
2465
  // Per-slot stdio modes (scr_child.c: 0 ignore, 1 inherit, 2 fd) and
2054
2466
  // the out/err fd expressions for mode 2 (the daemon-log idiom:
@@ -2058,12 +2470,13 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2058
2470
  let outFd = numLit(0, loc);
2059
2471
  let errFd = numLit(0, loc);
2060
2472
  let detached = boolLit(false, loc);
2473
+ let shell = boolLit(false, loc);
2474
+ let shellEnabled = false;
2061
2475
  let hasEnv = boolLit(false, loc);
2062
2476
  let envPairs = { kind: "arrayLit", elems: [], type: arrayOf(STRING), loc };
2063
2477
  let cwd = emptyStr;
2064
2478
  let plain = true; // exactly { stdio: "ignore" }: the historical libCall
2065
- const pipeFence = (node) => lowerer.noLowering('spawn with stdio: "pipe"', node, 'piped STDIN has no lowering — pipe stdout/stderr with the tuple form (stdio: ["ignore", "pipe", "pipe"]), or capture with spawnSync');
2066
- if (optsNode && ts.isObjectLiteralExpression(optsNode) && expr.arguments.length >= 2) {
2479
+ if (optsNode && ts.isObjectLiteralExpression(optsNode)) {
2067
2480
  for (const p of optsNode.properties) {
2068
2481
  // The conditional-spread idiom `...(isWindows ? {} : { detached:
2069
2482
  // true })` (either orientation): the one carried member supported
@@ -2099,18 +2512,15 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2099
2512
  }
2100
2513
  switch (m.name) {
2101
2514
  case "stdio": {
2102
- // The 3-tuple form: stdin a "ignore"/"inherit" literal,
2103
- // stdout/stderr each a literal (including "pipe" — the
2104
- // child.stdout/stderr stream slots) OR a number-typed fd (an
2105
- // openSync result — dup2'd into the child, Node's fd slots).
2515
+ // The 3-tuple form: every slot accepts a literal, including
2516
+ // "pipe" (child.stdin/stdout/stderr); stdout/stderr also accept
2517
+ // a number-typed fd (an openSync result — dup2'd into the child).
2106
2518
  if (ts.isArrayLiteralExpression(m.value) && m.value.elements.length === 3) {
2107
2519
  const slot = (el, which) => {
2108
2520
  const t = lowerer.typeOf(el);
2109
2521
  if (t.isStringLiteralType()) {
2110
- if (t.value === "pipe" && which === 0)
2111
- pipeFence(el);
2112
2522
  if (t.value !== "ignore" && t.value !== "inherit" && t.value !== "pipe") {
2113
- lowerer.noLowering(`spawn with stdio "${t.value}"`, el, '"ignore", "inherit", "pipe" (stdout/stderr), and number fds are the supported stdio slots');
2523
+ lowerer.noLowering(`spawn with stdio "${t.value}"`, el, '"ignore", "inherit", "pipe", and number output fds are the supported stdio slots');
2114
2524
  }
2115
2525
  const mode = t.value === "inherit" ? 1 : t.value === "pipe" ? 3 : 0;
2116
2526
  if (which === 0)
@@ -2124,7 +2534,7 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2124
2534
  return;
2125
2535
  }
2126
2536
  if (which === 0) {
2127
- lowerer.noLowering("spawn with this stdin slot", el, 'stdin takes "ignore" or "inherit" (fd stdin has no lowering)');
2537
+ lowerer.noLowering("spawn with this stdin slot", el, 'stdin takes "pipe", "ignore", or "inherit" (fd stdin has no lowering)');
2128
2538
  }
2129
2539
  if (lowerer.mapTypeOf(t)?.kind !== "f64") {
2130
2540
  lowerer.noLowering("spawn with this stdio option", el, 'each slot is "ignore", "inherit", or a number fd (an openSync result)');
@@ -2146,13 +2556,11 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2146
2556
  }
2147
2557
  const t = lowerer.typeOf(m.value);
2148
2558
  const v = t.isStringLiteralType() ? t.value : null;
2149
- if (v === "pipe")
2150
- pipeFence(m.value);
2151
- if (v !== "ignore" && v !== "inherit") {
2152
- lowerer.noLowering("spawn with this stdio option", m.value, '"ignore" and "inherit" are the supported stdio literals ' +
2153
- '(or a 3-tuple of those and number fds; "pipe" has no lowering)');
2559
+ if (v !== "pipe" && v !== "ignore" && v !== "inherit") {
2560
+ lowerer.noLowering("spawn with this stdio option", m.value, '"pipe", "ignore", and "inherit" are the supported stdio literals ' +
2561
+ "(or a 3-tuple of those and number output fds)");
2154
2562
  }
2155
- const mode = v === "inherit" ? 1 : 0;
2563
+ const mode = v === "inherit" ? 1 : v === "pipe" ? 3 : 0;
2156
2564
  inMode = outMode = errMode = mode;
2157
2565
  sawStdio = true;
2158
2566
  if (v !== "ignore")
@@ -2184,13 +2592,33 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2184
2592
  case "windowsHide":
2185
2593
  lowerer.lowerExpr(m.value); // Node no-op on POSIX
2186
2594
  break;
2595
+ case "shell":
2596
+ if (m.value.kind === ts.SyntaxKind.TrueKeyword) {
2597
+ shell = boolLit(true, loc);
2598
+ shellEnabled = true;
2599
+ plain = false;
2600
+ }
2601
+ else if (m.value.kind === ts.SyntaxKind.FalseKeyword) {
2602
+ shell = boolLit(false, loc);
2603
+ }
2604
+ else {
2605
+ lowerer.noLowering("spawn with a non-literal shell option", m.value, "shell must be a boolean literal");
2606
+ }
2607
+ break;
2187
2608
  default:
2188
- lowerer.noLowering(`spawn option '${m.name}'`, p, "stdio, detached, env, cwd, and windowsHide are the supported options");
2609
+ lowerer.noLowering(`spawn option '${m.name}'`, p, "stdio, detached, env, cwd, windowsHide, and shell are the supported options");
2189
2610
  }
2190
2611
  }
2191
2612
  }
2192
2613
  if (!sawStdio) {
2193
- lowerer.noLowering("spawn without { stdio: \"ignore\" }", expr, 'Node\'s default stdio is "pipe" (streams, no lowering) — pass { stdio: "ignore" } or { stdio: "inherit" } explicitly, or capture with spawnSync');
2614
+ inMode = outMode = errMode = 3;
2615
+ plain = false;
2616
+ }
2617
+ if (shellEnabled && argsNode !== undefined) {
2618
+ const shellArgs = stripTypeCasts(argsNode);
2619
+ if (!ts.isArrayLiteralExpression(shellArgs) || shellArgs.elements.length !== 0) {
2620
+ lowerer.noLowering("spawn with shell enabled and separate arguments", argsNode, "put the complete shell command in the first argument and pass [] (Node deprecates unescaped shell argument concatenation)");
2621
+ }
2194
2622
  }
2195
2623
  const argv = lowerer.lowerChildArgsArg(argsNode, loc);
2196
2624
  if (plain) {
@@ -2199,11 +2627,213 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2199
2627
  return {
2200
2628
  kind: "libCall",
2201
2629
  fn: "cp.spawnOpts",
2202
- args: [cmd, argv, numLit(inMode, loc), numLit(outMode, loc), numLit(errMode, loc), outFd, errFd, detached, hasEnv, envPairs, cwd],
2630
+ args: [cmd, argv, numLit(inMode, loc), numLit(outMode, loc), numLit(errMode, loc), outFd, errFd, detached, shell, hasEnv, envPairs, cwd],
2631
+ type: CHILD_T,
2632
+ loc,
2633
+ };
2634
+ }
2635
+ /** `fork(staticModulePath, args?, options?)` embeds the selected program
2636
+ * module and re-executes the native artifact with one private target id.
2637
+ * Node's default JSON IPC channel is always present. */
2638
+ export function lowerForkCall(lowerer, expr, loc) {
2639
+ if (expr.arguments.length < 1 || expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement)) {
2640
+ lowerer.noLowering("fork with this argument shape", expr, "use fork(staticModulePath[, stringArgs][, inlineOptions])");
2641
+ }
2642
+ const targetPath = staticForkModulePath(lowerer.program, expr.arguments[0]);
2643
+ const targetId = targetPath === null
2644
+ ? undefined
2645
+ : lowerer.forkTargetIdByPath.get(tsgoPath(resolve(targetPath)));
2646
+ if (targetId === undefined) {
2647
+ lowerer.noLowering("fork with a runtime-valued module path", expr.arguments[0], 'resolve a relative worker with new URL("./worker.ts", import.meta.url) or fileURLToPath(...) and bind it to a const if needed');
2648
+ }
2649
+ let argsNode;
2650
+ let optsNode;
2651
+ if (expr.arguments.length === 3) {
2652
+ argsNode = expr.arguments[1];
2653
+ optsNode = expr.arguments[2];
2654
+ }
2655
+ else if (expr.arguments.length === 2) {
2656
+ const second = expr.arguments[1];
2657
+ const secondType = lowerer.typeOf(second);
2658
+ const mapped = lowerer.mapTypeOf(secondType);
2659
+ if (lowerer.checker.isArrayType(secondType) ||
2660
+ lowerer.checker.isTupleType(secondType) ||
2661
+ (mapped?.kind === "array" && mapped.elem.kind === "string")) {
2662
+ argsNode = second;
2663
+ }
2664
+ else {
2665
+ optsNode = second;
2666
+ }
2667
+ }
2668
+ if (optsNode !== undefined && !ts.isObjectLiteralExpression(optsNode)) {
2669
+ lowerer.noLowering("fork with a non-literal options argument", optsNode, "pass cwd, env, execArgv, silent, stdio, serialization, and windowsHide in an inline object literal");
2670
+ }
2671
+ let inMode = 1;
2672
+ let outMode = 1;
2673
+ let errMode = 1;
2674
+ let sawStdio = false;
2675
+ let silent = false;
2676
+ let hasEnv = boolLit(false, loc);
2677
+ let envPairs = { kind: "arrayLit", elems: [], type: arrayOf(STRING), loc };
2678
+ let cwd = { kind: "strLit", value: "", type: STRING, loc };
2679
+ const stdioMode = (node, label) => {
2680
+ const type = lowerer.typeOf(node);
2681
+ const value = type.isStringLiteralType() ? type.value : null;
2682
+ if (value !== "ignore" && value !== "inherit" && value !== "pipe") {
2683
+ lowerer.noLowering(`fork with ${label} stdio`, node, '"ignore", "inherit", and "pipe" are the supported stdio modes');
2684
+ }
2685
+ return value === "ignore" ? 0 : value === "inherit" ? 1 : 3;
2686
+ };
2687
+ if (optsNode && ts.isObjectLiteralExpression(optsNode)) {
2688
+ for (const property of optsNode.properties) {
2689
+ const member = optionMember(property);
2690
+ if (!member) {
2691
+ lowerer.noLowering("fork with this options shape", property, "spreads and computed keys have no lowering — write each member inline");
2692
+ }
2693
+ switch (member.name) {
2694
+ case "cwd":
2695
+ cwd = lowerer.lowerExprExpecting(member.value, STRING);
2696
+ break;
2697
+ case "env":
2698
+ hasEnv = boolLit(true, loc);
2699
+ envPairs = lowerer.recordToEnvPairs(member.value);
2700
+ break;
2701
+ case "silent":
2702
+ if (member.value.kind === ts.SyntaxKind.TrueKeyword)
2703
+ silent = true;
2704
+ else if (member.value.kind !== ts.SyntaxKind.FalseKeyword) {
2705
+ lowerer.noLowering("fork with a non-literal silent option", member.value, "silent must be a boolean literal");
2706
+ }
2707
+ break;
2708
+ case "stdio": {
2709
+ if (ts.isArrayLiteralExpression(member.value)) {
2710
+ if (member.value.elements.length !== 4) {
2711
+ lowerer.noLowering("fork with this stdio tuple", member.value, 'use [stdin, stdout, stderr, "ipc"] with exactly four literal entries');
2712
+ }
2713
+ const ipc = member.value.elements[3];
2714
+ const ipcType = lowerer.typeOf(ipc);
2715
+ if (!ipcType.isStringLiteralType() || ipcType.value !== "ipc") {
2716
+ lowerer.noLowering("fork without a fourth-slot IPC channel", ipc, 'the fourth stdio entry must be "ipc"');
2717
+ }
2718
+ inMode = stdioMode(member.value.elements[0], "stdin");
2719
+ outMode = stdioMode(member.value.elements[1], "stdout");
2720
+ errMode = stdioMode(member.value.elements[2], "stderr");
2721
+ }
2722
+ else {
2723
+ const mode = stdioMode(member.value, "scalar");
2724
+ inMode = outMode = errMode = mode;
2725
+ }
2726
+ sawStdio = true;
2727
+ break;
2728
+ }
2729
+ case "serialization": {
2730
+ const type = lowerer.typeOf(member.value);
2731
+ if (!type.isStringLiteralType() || type.value !== "json") {
2732
+ lowerer.noLowering("fork with non-JSON serialization", member.value, 'the static IPC channel supports Node\'s default serialization or serialization: "json"');
2733
+ }
2734
+ break;
2735
+ }
2736
+ case "execArgv":
2737
+ // Loader flags configure the Node executable. The native worker is
2738
+ // already compiled into this artifact, so no loader starts.
2739
+ break;
2740
+ case "windowsHide":
2741
+ lowerer.lowerExpr(member.value); // accepted platform presentation option
2742
+ break;
2743
+ default:
2744
+ lowerer.noLowering(`fork with the '${member.name}' option`, member.value, "cwd, env, execArgv, silent, stdio, serialization, and windowsHide are supported");
2745
+ }
2746
+ }
2747
+ }
2748
+ if (!sawStdio && silent)
2749
+ inMode = outMode = errMode = 3;
2750
+ return {
2751
+ kind: "libCall",
2752
+ fn: "cp.fork",
2753
+ args: [
2754
+ numLit(targetId, loc),
2755
+ lowerChildArgsArg(lowerer, argsNode, loc),
2756
+ numLit(inMode, loc),
2757
+ numLit(outMode, loc),
2758
+ numLit(errMode, loc),
2759
+ hasEnv,
2760
+ envPairs,
2761
+ cwd,
2762
+ ],
2203
2763
  type: CHILD_T,
2204
2764
  loc,
2205
2765
  };
2206
2766
  }
2767
+ /** `execFile(file[, args][, options], callback)` — the asynchronous callback
2768
+ * slice. The child starts immediately with stdin/stdout/stderr piped; the
2769
+ * runtime captures both outputs and invokes the error-first callback after
2770
+ * settlement. The callback may ignore a suffix of `(error, stdout,
2771
+ * stderr)`, but every declared parameter must have the Node shape. Inline
2772
+ * `encoding: "utf8"` and a pure numeric `maxBuffer` are accepted; the latter
2773
+ * is not enforced by the growing native capture, as
2774
+ * with the synchronous and promisified capture forms. Other options remain
2775
+ * fenced until their lifecycle can share this asynchronous core. */
2776
+ export function lowerExecFileCall(lowerer, expr, loc) {
2777
+ if (expr.arguments.some(ts.isSpreadElement) || expr.arguments.length < 2 || expr.arguments.length > 4) {
2778
+ lowerer.noLowering(`execFile with ${expr.arguments.length} arguments`, expr, "the supported callback forms are execFile(file, callback), execFile(file, args, callback), and execFile(file, args, { encoding: 'utf8', maxBuffer: N }, callback)");
2779
+ }
2780
+ const cmd = lowerer.lowerExprExpecting(expr.arguments[0], STRING);
2781
+ const argsNode = expr.arguments.length >= 3 ? expr.arguments[1] : undefined;
2782
+ const callbackNode = expr.arguments[expr.arguments.length - 1];
2783
+ const argv = lowerer.lowerChildArgsArg(argsNode, loc);
2784
+ if (expr.arguments.length === 4) {
2785
+ const options = expr.arguments[2];
2786
+ if (!ts.isObjectLiteralExpression(options)) {
2787
+ lowerer.noLowering("execFile with a non-literal options argument", options, "pass encoding and maxBuffer in an inline object literal");
2788
+ }
2789
+ const pureNumber = (node) => ts.isNumericLiteral(node) ||
2790
+ (ts.isParenthesizedExpression(node) && pureNumber(node.expression)) ||
2791
+ (ts.isBinaryExpression(node) &&
2792
+ [ts.SyntaxKind.PlusToken, ts.SyntaxKind.MinusToken, ts.SyntaxKind.AsteriskToken, ts.SyntaxKind.SlashToken].includes(node.operatorToken.kind) &&
2793
+ pureNumber(node.left) && pureNumber(node.right));
2794
+ for (const property of options.properties) {
2795
+ const member = optionMember(property);
2796
+ if (!member)
2797
+ lowerer.noLowering("execFile with this options shape", property, "use plain encoding and maxBuffer properties without spreads or computed keys");
2798
+ if (member.name === "encoding") {
2799
+ if (!ts.isStringLiteral(member.value) || (member.value.text !== "utf8" && member.value.text !== "utf-8")) {
2800
+ lowerer.noLowering("execFile with a non-literal utf8 encoding", member.value, "pass the literal 'utf8' or 'utf-8' (other expressions might have side effects)");
2801
+ }
2802
+ }
2803
+ else if (member.name === "maxBuffer") {
2804
+ if (!pureNumber(member.value)) {
2805
+ lowerer.noLowering("execFile with a non-literal maxBuffer", member.value, "pass a numeric literal or literal arithmetic (the native capture grows without enforcing this limit)");
2806
+ }
2807
+ }
2808
+ else {
2809
+ lowerer.noLowering(`execFile option '${member.name}'`, property, "only encoding: 'utf8' and a pure numeric maxBuffer are supported in the callback form");
2810
+ }
2811
+ }
2812
+ }
2813
+ const callback = lowerer.lowerExpr(callbackNode);
2814
+ if (callback.type.kind !== "func" || callback.type.rest === true || callback.type.params.length > 3) {
2815
+ lowerer.unsupported("SC1090", callbackNode, "execFile callbacks take (error), (error, stdout), (error, stdout, stderr), or no parameters");
2816
+ }
2817
+ if (callback.type.ret.kind !== "void") {
2818
+ lowerer.unsupported("SC1090", callbackNode, "execFile callbacks returning a value (make the callback body a block, or return nothing)");
2819
+ }
2820
+ const errorParam = callback.type.params[0];
2821
+ if (errorParam !== undefined) {
2822
+ const def = errorParam.kind === "union" ? lowerer.unions.get(errorParam.unionId) : undefined;
2823
+ const valid = !!def && def.arms.length === 2 &&
2824
+ def.arms.some((arm) => arm.kind === "nullT") &&
2825
+ def.arms.some((arm) => arm.kind === "object" && arm.className === "%Error");
2826
+ if (!valid) {
2827
+ lowerer.unsupported("SC1090", callbackNode, `execFile callbacks whose first parameter is not 'Error | null' (got '${lowerer.fmt(errorParam)}')`);
2828
+ }
2829
+ }
2830
+ for (let i = 1; i < callback.type.params.length; i++) {
2831
+ if (callback.type.params[i].kind !== "string") {
2832
+ lowerer.unsupported("SC1090", callbackNode, `execFile callbacks whose ${i === 1 ? "stdout" : "stderr"} parameter is not 'string'`);
2833
+ }
2834
+ }
2835
+ return { kind: "libCall", fn: "cp.execFile", args: [cmd, argv, callback], type: CHILD_T, loc };
2836
+ }
2207
2837
  /** `execFileSync(file, args?, options?)` / `execSync(command, options?)`
2208
2838
  * → the ONE cp.execSync libCall. execSync wraps the command in
2209
2839
  * `/bin/sh -c` (Node's shell semantics — a single command string, no
@@ -2703,13 +3333,10 @@ function execStdioModeHelper(lowerer, shapeId, stdioT, fd, loc) {
2703
3333
  });
2704
3334
  return name;
2705
3335
  }
2706
- /** `const execFileAsync = promisify(execFile)` — the ONE lowered
2707
- * util.promisify shape. Returns true (and registers the declared symbol
2708
- * so call sites lower and value uses fence) when `init` is a promisify
2709
- * call over a child_process.execFile import binding; a promisify call
2710
- * over anything else fences HERE with the supported-target hint (the
2711
- * declaration is where the target is visible). False for non-promisify
2712
- * initializers, so the ordinary declaration paths apply. */
3336
+ /** Compile-time util.promisify projections. The immutable result binding
3337
+ * owns no runtime slot: calls rewrite to the target's promise lowering,
3338
+ * while value uses remain explicit fences. Unsupported targets report at
3339
+ * the declaration, where their identity is still visible. */
2713
3340
  export function isPromisifyCall(lowerer, init) {
2714
3341
  let e = init;
2715
3342
  while (ts.isParenthesizedExpression(e))
@@ -2723,7 +3350,38 @@ export function isPromisifyCall(lowerer, init) {
2723
3350
  return null;
2724
3351
  return e;
2725
3352
  }
2726
- export function promisifiedExecFileDecl(lowerer, nameNode, init) {
3353
+ /** Immutable aliases of table-backed builtin functions. The alias is a
3354
+ * compile-time call target, so direct calls keep the builtin's validated
3355
+ * lowering without materializing a JavaScript function object. Value
3356
+ * uses remain fenced until an escaping closure adapter exists. */
3357
+ export function registerBuiltinCallableAlias(lowerer, nameNode, init) {
3358
+ if (!ts.isIdentifier(nameNode) || !init)
3359
+ return false;
3360
+ let targetExpr = init;
3361
+ while (ts.isParenthesizedExpression(targetExpr))
3362
+ targetExpr = targetExpr.expression;
3363
+ if (!ts.isIdentifier(targetExpr))
3364
+ return false;
3365
+ const sourceSymbol = lowerer.resolveValueSymbol(targetExpr);
3366
+ const existing = sourceSymbol ? lowerer.staticCallables.get(sourceSymbol) : undefined;
3367
+ const target = existing?.kind === "builtin-function"
3368
+ ? { module: existing.module, member: existing.member }
3369
+ : lowerer.builtinImportOf(targetExpr);
3370
+ if (!target || !builtinModuleFnOf(lowerer, target.module, target.member))
3371
+ return false;
3372
+ if (lowerer.checker.getCallSignatures(lowerer.typeOf(nameNode)).length === 0)
3373
+ return false;
3374
+ const symbol = lowerer.checker.getSymbolAtLocation(nameNode);
3375
+ if (!symbol)
3376
+ return false;
3377
+ lowerer.staticCallables.set(symbol, {
3378
+ kind: "builtin-function",
3379
+ module: target.module,
3380
+ member: target.member,
3381
+ });
3382
+ return true;
3383
+ }
3384
+ export function registerPromisifiedBuiltinDecl(lowerer, nameNode, init) {
2727
3385
  if (!init)
2728
3386
  return false;
2729
3387
  // The OTHER special const-binding form this decl hook serves: the
@@ -2737,12 +3395,17 @@ export function promisifiedExecFileDecl(lowerer, nameNode, init) {
2737
3395
  return false;
2738
3396
  const argNode = e.arguments.length === 1 ? e.arguments[0] : null;
2739
3397
  const target = argNode && ts.isIdentifier(argNode) ? lowerer.builtinImportOf(argNode) : null;
2740
- if (!target || target.module !== "child_process" || target.member !== "execFile") {
2741
- lowerer.noLowering("util.promisify of this target", argNode ?? e, "child_process.execFile is the one promisifiable target: const execFileAsync = promisify(execFile)");
3398
+ const projection = target?.module === "child_process" && target.member === "execFile"
3399
+ ? { kind: "promisified-exec-file" }
3400
+ : target?.module === "fs" && target.member === "readFile"
3401
+ ? { kind: "promisified-builtin", module: "fs/promises", member: "readFile" }
3402
+ : null;
3403
+ if (!projection) {
3404
+ lowerer.noLowering("util.promisify of this target", argNode ?? e, "child_process.execFile and fs.readFile are the supported targets");
2742
3405
  }
2743
3406
  const symbol = lowerer.checker.getSymbolAtLocation(nameNode);
2744
3407
  if (symbol)
2745
- lowerer.promisifiedExecFile.add(symbol);
3408
+ lowerer.staticCallables.set(symbol, projection);
2746
3409
  return true;
2747
3410
  }
2748
3411
  /** A call THROUGH a promisified-execFile binding:
@@ -3730,9 +4393,9 @@ export function fencedBuiltinImportOf(lowerer, ident) {
3730
4393
  /** The composed crypto pattern: `randomBytes(n).toString(enc)` lowers
3731
4394
  * as ONE string-producing libCall — the Buffer between the two calls
3732
4395
  * never exists at runtime. Only literal "hex"/"base64" encodings lower
3733
- * (the runtime implements exactly those); everything else — including a
3734
- * bare randomBytes(n) — fences with the Buffer story. Null when this
3735
- * isn't a toString on a crypto.randomBytes call. */
4396
+ * (the runtime implements exactly those); wider forms fall through to
4397
+ * the ordinary Buffer lowering. Null when this is not a toString on a
4398
+ * crypto.randomBytes call. */
3736
4399
  export function lowerCryptoComposedCall(lowerer, call, access) {
3737
4400
  if (call.questionDotToken || access.questionDotToken)
3738
4401
  return null;
@@ -3766,16 +4429,9 @@ export function lowerCryptoComposedCall(lowerer, call, access) {
3766
4429
  const enc = lowerer.lowerExprExpecting(encNode, STRING);
3767
4430
  return { kind: "libCall", fn: "crypto.randomBytesToString", args: [size, enc], type: STRING, loc };
3768
4431
  }
3769
- /** The composed hash chain — `createHash("sha256").update(data).digest("hex")`
3770
- * — fused into ONE libCall: the Hash handle never materializes (no Hash
3771
- * type exists in the value model), exactly the randomBytesToString
3772
- * stance. Both import spellings reach here (the named `createHash(...)`
3773
- * and the namespace `crypto.createHash(...)`). Once the chain is
3774
- * recognized, the narrow forms FENCE with pointed hints instead of
3775
- * falling to the generic member fence: sha256 is the lowered algorithm,
3776
- * one string- or Buffer-typed update, hex digests. Null when the callee
3777
- * isn't this chain at all (other Hash-typed code lands on the ordinary
3778
- * Hash.<member> fences). */
4432
+ /** Fast path for the exact single-update hash chain. Wider algorithms,
4433
+ * stored handles, multiple updates, Buffer digests, and input encodings
4434
+ * fall through to the first-class Hash lowering below. */
3779
4435
  function lowerHashDigestChain(lowerer, call, access) {
3780
4436
  const updateCall = access.expression;
3781
4437
  if (!ts.isCallExpression(updateCall) || updateCall.questionDotToken)
@@ -3799,16 +4455,14 @@ function lowerHashDigestChain(lowerer, call, access) {
3799
4455
  return null;
3800
4456
  const loc = locOf(call);
3801
4457
  const algT = chCall.arguments.length === 1 ? lowerer.typeOf(chCall.arguments[0]) : undefined;
3802
- if (!algT?.isStringLiteralType() || (algT.value !== "sha256" && algT.value !== "sha1")) {
3803
- lowerer.noLowering("createHash with this algorithm", chCall, 'sha256 and sha1 are the lowered algorithms: createHash("sha256") ' +
3804
- "(sha1 exists for the RFC 6455 Sec-WebSocket-Accept hash)");
3805
- }
4458
+ if (!algT?.isStringLiteralType() || (algT.value !== "sha256" && algT.value !== "sha1"))
4459
+ return null;
3806
4460
  if (updateCall.arguments.length !== 1) {
3807
- lowerer.noLowering(`Hash.update with ${updateCall.arguments.length} arguments`, updateCall, "one string or Buffer argument is the lowered update (input encodings have no lowering)");
4461
+ return null;
3808
4462
  }
3809
4463
  const encT = call.arguments.length === 1 ? lowerer.typeOf(call.arguments[0]) : undefined;
3810
4464
  if (!encT?.isStringLiteralType() || (encT.value !== "hex" && encT.value !== "base64")) {
3811
- lowerer.noLowering("Hash.digest with this encoding", call, 'hex and base64 are the lowered digests: .digest("hex") (the bare Buffer digest has no lowering)');
4465
+ return null;
3812
4466
  }
3813
4467
  // alg and enc are proven literals (fenced above), so lowering them
3814
4468
  // out of source position observes nothing; the data lowers between
@@ -3829,22 +4483,194 @@ function lowerHashDigestChain(lowerer, call, access) {
3829
4483
  const enc = lowerer.lowerExprExpecting(call.arguments[0], STRING);
3830
4484
  return { kind: "libCall", fn: "crypto.hashDigestStr", args: [alg, data, enc], type: STRING, loc };
3831
4485
  }
3832
- lowerer.noLowering(`Hash.update of '${dataIr ? lowerer.fmt(dataIr) : lowerer.checker.typeToString(lowerer.typeOf(dataNode))}' values`, dataNode, "string and Buffer/Uint8Array inputs are the lowered update forms");
3833
- }
3834
- /** The node:crypto introspection statics — build-time constants of the
3835
- * compiled runtime, baked at the call site (the http2.constants stance
3836
- * extended to calls): getFips() answers 0 (no FIPS provider can ever
3837
- * load into a compiled binary — Node's own answer for a non-FIPS
3838
- * build), and getCiphers()/getHashes()/getCurves() answer Node v24's
3839
- * name lists as fresh string[] literals. The lists are INTROSPECTION
3840
- * data (Node's contract is "names the provider recognizes"); the
3841
- * operations behind the names keep their per-member fences — a program
3842
- * that probes the list and then constructs a cipher fences at the
3843
- * construction site, never here. Null for other members (the dispatch
3844
- * keeps trying). */
4486
+ return null;
4487
+ }
4488
+ function cryptoEncoding(lowerer, node, use) {
4489
+ const type = lowerer.typeOf(node);
4490
+ if (!type.isStringLiteralType() || (type.value !== "hex" && type.value !== "base64")) {
4491
+ lowerer.noLowering(`${use} with this encoding`, node, 'the lowered output encodings are "hex" and "base64"');
4492
+ }
4493
+ return lowerer.lowerExprExpecting(node, STRING);
4494
+ }
4495
+ function cryptoAlgorithm(lowerer, node, use) {
4496
+ const type = lowerer.typeOf(node);
4497
+ if (type.isStringLiteralType() && !["md5", "sha1", "sha256"].includes(type.value.toLowerCase())) {
4498
+ lowerer.noLowering(`${use} with algorithm '${type.value}'`, node, "md5, sha1, and sha256 are the lowered digest algorithms");
4499
+ }
4500
+ return lowerer.lowerExprExpecting(node, STRING);
4501
+ }
4502
+ function cryptoInputBytes(lowerer, node, loc) {
4503
+ const value = lowerer.lowerExpr(node);
4504
+ if (value.type.kind === "bytes" && value.type.elem === "u8")
4505
+ return value;
4506
+ if (value.type.kind === "string") {
4507
+ return {
4508
+ kind: "libCall",
4509
+ fn: "buffer.fromStr",
4510
+ args: [value, { kind: "strLit", value: "utf8", type: STRING, loc }],
4511
+ type: BYTES_U8,
4512
+ loc,
4513
+ };
4514
+ }
4515
+ lowerer.noLowering(`crypto byte input of '${lowerer.fmt(value.type)}' values`, node, "string and Buffer/Uint8Array values are supported");
4516
+ }
4517
+ function errorFirstBytesCallback(lowerer, node, api) {
4518
+ let callback = lowerer.lowerExpr(node);
4519
+ if (callback.type.kind === "dyn") {
4520
+ callback = { kind: "dynCheck", value: callback, type: funcOf([DYN, DYN], VOID), loc: locOf(node) };
4521
+ }
4522
+ if (callback.type.kind !== "func" || callback.type.params.length > 2) {
4523
+ lowerer.unsupported("SC1090", node, `${api} callbacks must accept at most (error, buffer)`);
4524
+ }
4525
+ const error = callback.type.params[0];
4526
+ if (error !== undefined && error.kind !== "dyn") {
4527
+ if (error.kind !== "union") {
4528
+ lowerer.unsupported("SC1090", node, `${api} callback error parameters must be Error | null`);
4529
+ }
4530
+ const def = lowerer.unions.get(error.unionId);
4531
+ const valid = !!def &&
4532
+ def.arms.some((arm) => arm.kind === "nullT") &&
4533
+ def.arms.some((arm) => arm.kind === "object" && arm.className === "%Error") &&
4534
+ def.arms.every((arm) => arm.kind === "nullT" || arm.kind === "undefinedT" || (arm.kind === "object" && arm.className === "%Error"));
4535
+ if (!valid)
4536
+ lowerer.unsupported("SC1090", node, `${api} callback error parameters must be Error | null`);
4537
+ }
4538
+ const value = callback.type.params[1];
4539
+ if (value !== undefined && value.kind !== "dyn" && !(value.kind === "bytes" && value.elem === "u8")) {
4540
+ lowerer.unsupported("SC1090", node, `${api} callback result parameters must be Buffer/Uint8Array values`);
4541
+ }
4542
+ return voidizedCallback(lowerer, callback, locOf(node));
4543
+ }
4544
+ /** The node:crypto utility and introspection statics. Hash/Hmac are real
4545
+ * native handles; one-shot hashing, constant-time equality, random-fill/
4546
+ * integer, and PBKDF2 share the same runtime primitives as the island.
4547
+ * The name lists remain build-time constants. */
3845
4548
  export function lowerCryptoModuleCall(lowerer, expr, bi, loc) {
3846
4549
  if (bi.module !== "crypto")
3847
4550
  return null;
4551
+ const args = expr.arguments;
4552
+ if (args.some(ts.isSpreadElement)) {
4553
+ lowerer.noLowering(`crypto.${bi.member} with spread arguments`, expr);
4554
+ }
4555
+ if (bi.member === "randomBytes") {
4556
+ if (args.length === 1)
4557
+ return null;
4558
+ if (args.length !== 2) {
4559
+ lowerer.noLowering(`crypto.randomBytes with ${args.length} arguments`, expr, "randomBytes(size) and randomBytes(size, callback) are supported");
4560
+ }
4561
+ return {
4562
+ kind: "libCall",
4563
+ fn: "crypto.randomBytesCb",
4564
+ args: [lowerer.lowerExprExpecting(args[0], F64), errorFirstBytesCallback(lowerer, args[1], "crypto")],
4565
+ type: VOID,
4566
+ loc,
4567
+ };
4568
+ }
4569
+ if (bi.member === "pbkdf2") {
4570
+ if (args.length !== 6) {
4571
+ lowerer.noLowering(`crypto.pbkdf2 with ${args.length} arguments`, expr, "pbkdf2(password, salt, iterations, keylen, digest, callback) is supported");
4572
+ }
4573
+ return {
4574
+ kind: "libCall",
4575
+ fn: "crypto.pbkdf2Cb",
4576
+ args: [
4577
+ cryptoInputBytes(lowerer, args[0], locOf(args[0])),
4578
+ cryptoInputBytes(lowerer, args[1], locOf(args[1])),
4579
+ lowerer.lowerExprExpecting(args[2], F64),
4580
+ lowerer.lowerExprExpecting(args[3], F64),
4581
+ cryptoAlgorithm(lowerer, args[4], "crypto.pbkdf2"),
4582
+ errorFirstBytesCallback(lowerer, args[5], "crypto"),
4583
+ ],
4584
+ type: VOID,
4585
+ loc,
4586
+ };
4587
+ }
4588
+ if (bi.member === "createHash") {
4589
+ if (args.length !== 1) {
4590
+ lowerer.noLowering(`crypto.createHash with ${args.length} arguments`, expr, "createHash(algorithm) is supported; options are not yet lowered");
4591
+ }
4592
+ return { kind: "libCall", fn: "crypto.hashNew", args: [cryptoAlgorithm(lowerer, args[0], "crypto.createHash")], type: CRYPTOHASH_T, loc };
4593
+ }
4594
+ if (bi.member === "createHmac") {
4595
+ if (args.length !== 2) {
4596
+ lowerer.noLowering(`crypto.createHmac with ${args.length} arguments`, expr, "createHmac(algorithm, stringOrBufferKey) is supported; options and KeyObject keys are not yet lowered");
4597
+ }
4598
+ const algorithm = cryptoAlgorithm(lowerer, args[0], "crypto.createHmac");
4599
+ const key = lowerer.lowerExpr(args[1]);
4600
+ if (key.type.kind === "string") {
4601
+ return { kind: "libCall", fn: "crypto.hmacNewStr", args: [algorithm, key], type: CRYPTOHMAC_T, loc };
4602
+ }
4603
+ if (key.type.kind === "bytes" && key.type.elem === "u8") {
4604
+ return { kind: "libCall", fn: "crypto.hmacNewBytes", args: [algorithm, key], type: CRYPTOHMAC_T, loc };
4605
+ }
4606
+ lowerer.noLowering(`crypto.createHmac with a '${lowerer.fmt(key.type)}' key`, args[1], "string and Buffer/Uint8Array keys are supported");
4607
+ }
4608
+ if (bi.member === "hash") {
4609
+ if (args.length < 2 || args.length > 3) {
4610
+ lowerer.noLowering(`crypto.hash with ${args.length} arguments`, expr, "hash(algorithm, stringOrBuffer[, \"hex\" | \"base64\"]) is supported");
4611
+ }
4612
+ const algorithm = cryptoAlgorithm(lowerer, args[0], "crypto.hash");
4613
+ const data = lowerer.lowerExpr(args[1]);
4614
+ const encoding = args[2]
4615
+ ? cryptoEncoding(lowerer, args[2], "crypto.hash")
4616
+ : { kind: "strLit", value: "hex", type: STRING, loc };
4617
+ if (data.type.kind === "string") {
4618
+ return { kind: "libCall", fn: "crypto.hashDigestStr", args: [algorithm, data, encoding], type: STRING, loc };
4619
+ }
4620
+ if (data.type.kind === "bytes" && data.type.elem === "u8") {
4621
+ return { kind: "libCall", fn: "crypto.hashDigestBytes", args: [algorithm, data, encoding], type: STRING, loc };
4622
+ }
4623
+ lowerer.noLowering(`crypto.hash of '${lowerer.fmt(data.type)}' data`, args[1], "string and Buffer/Uint8Array data are supported");
4624
+ }
4625
+ if (bi.member === "timingSafeEqual") {
4626
+ if (args.length !== 2)
4627
+ lowerer.noLowering(`crypto.timingSafeEqual with ${args.length} arguments`, expr);
4628
+ return {
4629
+ kind: "libCall",
4630
+ fn: "crypto.timingSafeEqual",
4631
+ args: [lowerer.lowerExprExpecting(args[0], BYTES_U8), lowerer.lowerExprExpecting(args[1], BYTES_U8)],
4632
+ type: BOOL,
4633
+ loc,
4634
+ };
4635
+ }
4636
+ if (bi.member === "randomFillSync") {
4637
+ if (args.length < 1 || args.length > 3) {
4638
+ lowerer.noLowering(`crypto.randomFillSync with ${args.length} arguments`, expr, "randomFillSync(buffer[, offset[, size]]) is supported");
4639
+ }
4640
+ const buffer = lowerer.lowerExprExpecting(args[0], BYTES_U8);
4641
+ const offset = args[1]
4642
+ ? lowerer.lowerExprExpecting(args[1], F64)
4643
+ : { kind: "numLit", value: 0, type: F64, loc };
4644
+ if (args[2]) {
4645
+ const size = lowerer.lowerExprExpecting(args[2], F64);
4646
+ return { kind: "libCall", fn: "crypto.randomFill", args: [buffer, offset, size], type: BYTES_U8, loc };
4647
+ }
4648
+ return { kind: "libCall", fn: "crypto.randomFillRest", args: [buffer, offset], type: BYTES_U8, loc };
4649
+ }
4650
+ if (bi.member === "randomInt") {
4651
+ if (args.length !== 1 && args.length !== 2) {
4652
+ lowerer.noLowering(`crypto.randomInt with ${args.length} arguments`, expr, "the synchronous randomInt(max) and randomInt(min, max) forms are supported");
4653
+ }
4654
+ const min = args.length === 1
4655
+ ? { kind: "numLit", value: 0, type: F64, loc }
4656
+ : lowerer.lowerExprExpecting(args[0], F64);
4657
+ const max = lowerer.lowerExprExpecting(args[args.length - 1], F64);
4658
+ return { kind: "libCall", fn: "crypto.randomInt", args: [min, max], type: F64, loc };
4659
+ }
4660
+ if (bi.member === "pbkdf2Sync") {
4661
+ if (args.length !== 5) {
4662
+ lowerer.noLowering(`crypto.pbkdf2Sync with ${args.length} arguments`, expr, "pbkdf2Sync(password, salt, iterations, keylen, digest) is supported");
4663
+ }
4664
+ const password = cryptoInputBytes(lowerer, args[0], locOf(args[0]));
4665
+ const salt = cryptoInputBytes(lowerer, args[1], locOf(args[1]));
4666
+ return {
4667
+ kind: "libCall",
4668
+ fn: "crypto.pbkdf2",
4669
+ args: [password, salt, lowerer.lowerExprExpecting(args[2], F64), lowerer.lowerExprExpecting(args[3], F64), cryptoAlgorithm(lowerer, args[4], "crypto.pbkdf2Sync")],
4670
+ type: BYTES_U8,
4671
+ loc,
4672
+ };
4673
+ }
3848
4674
  const LISTS = {
3849
4675
  getCiphers: CRYPTO_CIPHERS,
3850
4676
  getHashes: CRYPTO_HASHES,
@@ -3881,7 +4707,7 @@ export function lowerUrlMethodCall(lowerer, call, access) {
3881
4707
  const receiver = lowerer.lowerExpr(access.expression);
3882
4708
  return { kind: "libCall", fn: "url.href", args: [receiver], type: STRING, loc: locOf(call) };
3883
4709
  }
3884
- lowerer.noLowering(`URL.${name}`, call, "protocol, pathname, href, and toString() are the supported URL members", lowerer.checker.getSymbolAtLocation(access.name));
4710
+ lowerer.noLowering(`URL.${name}`, call, "protocol, origin, username, password, pathname, href, host, hostname, port, search, hash, searchParams, and toString() are the supported URL members", lowerer.checker.getSymbolAtLocation(access.name));
3885
4711
  }
3886
4712
  /** `new URLSearchParams(init?)` — the WHATWG constructor's lowered init
3887
4713
  * shapes: omitted / literal `undefined` (empty list), a string (parsed,
@@ -4463,7 +5289,86 @@ export function lowerStatsMethodCall(lowerer, call, access) {
4463
5289
  : "stats.isSymbolicLink";
4464
5290
  return { kind: "libCall", fn, args: [receiver], type: BOOL, loc: locOf(call) };
4465
5291
  }
4466
- lowerer.noLowering(`Stats.${name}`, call, "isFile(), isDirectory(), isSymbolicLink(), size, blocks, nlink, atimeMs, and mtimeMs are the supported Stats members", lowerer.checker.getSymbolAtLocation(access.name));
5292
+ lowerer.noLowering(`Stats.${name}`, call, "isFile(), isDirectory(), isSymbolicLink(), dev, ino, size, blocks, nlink, atimeMs, mtimeMs, and ctimeMs are the supported Stats members", lowerer.checker.getSymbolAtLocation(access.name));
5293
+ }
5294
+ /** Calls on native crypto.Hash/Hmac handles. The handle survives locals,
5295
+ * aliases, loops, and returns; update() retains and returns the receiver,
5296
+ * digest() finalizes it, and Hash.copy() snapshots the incremental state. */
5297
+ export function lowerCryptoHashMethodCall(lowerer, call, access) {
5298
+ if (call.questionDotToken || access.questionDotToken)
5299
+ return null;
5300
+ const receiverType = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
5301
+ if (receiverType?.kind !== "cryptoHash" && receiverType?.kind !== "cryptoHmac")
5302
+ return null;
5303
+ if (!lowerer.isStdlibMember(access))
5304
+ return null;
5305
+ const name = access.name.text;
5306
+ const loc = locOf(call);
5307
+ const receiver = () => lowerer.lowerExprExpecting(access.expression, receiverType);
5308
+ if (name === "update") {
5309
+ if (call.arguments.length < 1 || call.arguments.length > 2 || call.arguments.some(ts.isSpreadElement)) {
5310
+ lowerer.noLowering(`${receiverType.kind === "cryptoHash" ? "Hash" : "Hmac"}.update with ${call.arguments.length} arguments`, call, "update(stringOrBuffer[, inputEncoding]) is supported");
5311
+ }
5312
+ const dataNode = call.arguments[0];
5313
+ const data = lowerer.lowerExpr(dataNode);
5314
+ const prefix = receiverType.kind === "cryptoHash" ? "crypto.hashUpdate" : "crypto.hmacUpdate";
5315
+ if (data.type.kind === "bytes" && data.type.elem === "u8") {
5316
+ if (call.arguments.length !== 1) {
5317
+ lowerer.noLowering("Hash/Hmac.update with an encoding for Buffer data", call.arguments[1], "input encodings apply only to string data");
5318
+ }
5319
+ return { kind: "libCall", fn: `${prefix}Bytes`, args: [receiver(), data], type: receiverType, loc };
5320
+ }
5321
+ if (data.type.kind === "string") {
5322
+ const encodingNode = call.arguments[1];
5323
+ if (encodingNode === undefined) {
5324
+ return { kind: "libCall", fn: `${prefix}Str`, args: [receiver(), data], type: receiverType, loc };
5325
+ }
5326
+ const encodingType = lowerer.typeOf(encodingNode);
5327
+ if (!encodingType.isStringLiteralType() || !["utf8", "utf-8", "hex", "base64"].includes(encodingType.value)) {
5328
+ lowerer.noLowering("Hash/Hmac.update with this input encoding", encodingNode, 'utf8, hex, and base64 string inputs are supported');
5329
+ }
5330
+ if (encodingType.value === "utf8" || encodingType.value === "utf-8") {
5331
+ lowerer.lowerExprExpecting(encodingNode, STRING);
5332
+ return { kind: "libCall", fn: `${prefix}Str`, args: [receiver(), data], type: receiverType, loc };
5333
+ }
5334
+ const encoded = {
5335
+ kind: "libCall",
5336
+ fn: "buffer.fromStr",
5337
+ args: [data, lowerer.lowerExprExpecting(encodingNode, STRING)],
5338
+ type: BYTES_U8,
5339
+ loc,
5340
+ };
5341
+ return { kind: "libCall", fn: `${prefix}Bytes`, args: [receiver(), encoded], type: receiverType, loc };
5342
+ }
5343
+ lowerer.noLowering(`Hash/Hmac.update of '${lowerer.fmt(data.type)}' values`, dataNode, "string and Buffer/Uint8Array inputs are supported");
5344
+ }
5345
+ if (name === "digest") {
5346
+ if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
5347
+ lowerer.noLowering(`${receiverType.kind === "cryptoHash" ? "Hash" : "Hmac"}.digest with ${call.arguments.length} arguments`, call, 'digest() and digest("hex" | "base64") are supported');
5348
+ }
5349
+ if (call.arguments.length === 0) {
5350
+ return {
5351
+ kind: "libCall",
5352
+ fn: receiverType.kind === "cryptoHash" ? "crypto.hashDigestBuffer" : "crypto.hmacDigestBuffer",
5353
+ args: [receiver()],
5354
+ type: BYTES_U8,
5355
+ loc,
5356
+ };
5357
+ }
5358
+ return {
5359
+ kind: "libCall",
5360
+ fn: receiverType.kind === "cryptoHash" ? "crypto.hashDigestString" : "crypto.hmacDigestString",
5361
+ args: [receiver(), cryptoEncoding(lowerer, call.arguments[0], `${receiverType.kind === "cryptoHash" ? "Hash" : "Hmac"}.digest`)],
5362
+ type: STRING,
5363
+ loc,
5364
+ };
5365
+ }
5366
+ if (name === "copy" && receiverType.kind === "cryptoHash") {
5367
+ if (call.arguments.length !== 0)
5368
+ lowerer.noLowering(`Hash.copy with ${call.arguments.length} arguments`, call, "copy() without options is supported");
5369
+ return { kind: "libCall", fn: "crypto.hashCopy", args: [receiver()], type: CRYPTOHASH_T, loc };
5370
+ }
5371
+ lowerer.noLowering(`${receiverType.kind === "cryptoHash" ? "Hash" : "Hmac"}.${name}`, call, receiverType.kind === "cryptoHash" ? "update(), digest(), and copy() are supported" : "update() and digest() are supported", lowerer.checker.getSymbolAtLocation(access.name));
4467
5372
  }
4468
5373
  /** `Atomics.wait(int32Array, idx, expected, timeoutMs)` — the
4469
5374
  * synchronous-sleep idiom (RouteStore's
@@ -4520,7 +5425,7 @@ export function lowerBuiltinExtraProperty(lowerer, expr) {
4520
5425
  return { kind: "recordGet", obj: receiver, shapeId: receiver.type.shapeId, field: "%enc", type: STRING, loc: locOf(expr) };
4521
5426
  }
4522
5427
  const kind = lowerer.mapTypeOf(lowerer.typeOf(expr.expression))?.kind;
4523
- if (kind !== "stats" && kind !== "fileHandle" && kind !== "spawnRes" && kind !== "child")
5428
+ if (kind !== "stats" && kind !== "fileHandle" && kind !== "spawnRes" && kind !== "child" && kind !== "childWriter")
4524
5429
  return null;
4525
5430
  if (kind === "child" ? !isChildSurfaceMember(lowerer, expr) : !lowerer.isStdlibMember(expr))
4526
5431
  return null;
@@ -4537,29 +5442,50 @@ export function lowerBuiltinExtraProperty(lowerer, expr) {
4537
5442
  }
4538
5443
  lowerer.noLowering(`FileHandle.${name}`, expr, "fd, close(), read(), write(), readFile(), writeFile(), appendFile(), and stat() are the supported FileHandle members", lowerer.checker.getSymbolAtLocation(expr.name));
4539
5444
  }
4540
- // child.stdout / child.stderr — the piped-output streams: the
4541
- // checker's `Readable | null` (null exactly when the slot was not
4542
- // piped), constructed type-directedly in the backend over the
5445
+ // child.stdin/stdout/stderr — the piped streams: the checker's
5446
+ // `Writable | null` / `Readable | null` (null exactly when the slot
5447
+ // was not piped), constructed type-directedly in the backend over the
4543
5448
  // +1-or-NULL runtime pair.
4544
- if (kind === "child" && (name === "stdout" || name === "stderr")) {
5449
+ if (kind === "child" && (name === "stdin" || name === "stdout" || name === "stderr")) {
4545
5450
  const receiver = lowerer.lowerExpr(expr.expression);
5451
+ const streamType = name === "stdin" ? CHILDWRITER_T : CHILDSTREAM_T;
4546
5452
  const type = {
4547
5453
  kind: "union",
4548
- unionId: lowerer.unions.intern([CHILDSTREAM_T, { kind: "nullT" }]),
5454
+ unionId: lowerer.unions.intern([streamType, { kind: "nullT" }]),
4549
5455
  };
4550
5456
  const read = {
4551
5457
  kind: "libCall",
4552
- fn: name === "stdout" ? "child.stdout" : "child.stderr",
5458
+ fn: name === "stdin" ? "child.stdin" : name === "stdout" ? "child.stdout" : "child.stderr",
4553
5459
  args: [receiver],
4554
5460
  type,
4555
5461
  loc,
4556
5462
  };
4557
5463
  return lowerer.maybeNarrow(read, expr);
4558
5464
  }
5465
+ if (kind === "child" && name === "connected") {
5466
+ return {
5467
+ kind: "libCall",
5468
+ fn: "child.connected",
5469
+ args: [lowerer.lowerExpr(expr.expression)],
5470
+ type: BOOL,
5471
+ loc,
5472
+ };
5473
+ }
4559
5474
  if (kind === "child")
4560
5475
  return null; // pid/exitCode/killed live in lowerIntrinsicProperty
5476
+ if (kind === "childWriter") {
5477
+ if (name === "writable") {
5478
+ const receiver = lowerer.lowerExprExpecting(expr.expression, CHILDWRITER_T);
5479
+ return { kind: "libCall", fn: "writer.writable", args: [receiver], type: BOOL, loc };
5480
+ }
5481
+ if (name === "write" || name === "end" || name === "destroy" || name === "on" || name === "once") {
5482
+ lowerer.unsupported("SC1090", expr, `child stdin methods as values (call '${name}' directly)`);
5483
+ }
5484
+ lowerer.noLowering(`Writable.${name}`, expr, 'write(string | Uint8Array), end(), destroy(), writable, and on/once("drain" | "finish" | "error", cb) are supported', lowerer.checker.getSymbolAtLocation(expr.name));
5485
+ }
4561
5486
  if (kind === "stats" &&
4562
- (name === "blocks" || name === "nlink" || name === "atimeMs" || name === "mtimeMs")) {
5487
+ (name === "dev" || name === "ino" || name === "blocks" || name === "nlink" ||
5488
+ name === "atimeMs" || name === "mtimeMs" || name === "ctimeMs")) {
4563
5489
  const receiver = lowerer.lowerExpr(expr.expression);
4564
5490
  const fn = `stats.${name}`;
4565
5491
  return { kind: "libCall", fn, args: [receiver], type: F64, loc };
@@ -4586,6 +5512,63 @@ export function lowerBuiltinExtraProperty(lowerer, expr) {
4586
5512
  * lowerIntrinsicProperty). Everything else @types/node declares on
4587
5513
  * ChildProcess (stdout, once, ...) fences member-qualified. Null for
4588
5514
  * non-child receivers. */
5515
+ function ignoredListenerReturn(type) {
5516
+ return type.kind === "void" || (type.kind === "promise" && type.inner.kind === "void");
5517
+ }
5518
+ function ipcJsonMessage(lowerer, node, loc) {
5519
+ const value = lowerer.lowerExpr(node);
5520
+ if (!lowerer.jsonStringifySafe(value.type) && value.type.kind !== "dyn") {
5521
+ lowerer.unsupported("SC1090", node, `IPC messages of '${lowerer.fmt(value.type)}' values (messages must be JSON-stringifiable records, arrays, primitives, unions of those, or unknown)`);
5522
+ }
5523
+ return { kind: "jsonStringify", value, type: STRING, loc };
5524
+ }
5525
+ function ipcMessageListener(lowerer, node) {
5526
+ const cb = lowerer.lowerExpr(node);
5527
+ if (cb.type.kind !== "func" || cb.type.params.length > 1 || !ignoredListenerReturn(cb.type.ret)) {
5528
+ lowerer.unsupported("SC1090", node, "message listeners take zero or one parameter and may return void or Promise<void>");
5529
+ }
5530
+ const param = cb.type.params[0];
5531
+ if (param !== undefined && param.kind !== "dyn" && !lowerer.jsonSafe(param)) {
5532
+ lowerer.unsupported("SC1090", node, `message listeners whose parameter is not JSON-shaped or unknown (got '${lowerer.fmt(param)}')`);
5533
+ }
5534
+ return cb;
5535
+ }
5536
+ function ipcDisconnectListener(lowerer, node) {
5537
+ const cb = lowerer.lowerExpr(node);
5538
+ if (cb.type.kind !== "func" || cb.type.params.length !== 0 || cb.type.ret.kind !== "void") {
5539
+ lowerer.unsupported("SC1090", node, "disconnect listeners take no parameters and return void");
5540
+ }
5541
+ return cb;
5542
+ }
5543
+ function ipcSendCallback(lowerer, node) {
5544
+ const cb = lowerer.lowerExpr(node);
5545
+ if (cb.type.kind !== "func" || cb.type.params.length > 1 || cb.type.ret.kind !== "void") {
5546
+ lowerer.unsupported("SC1090", node, "send callbacks take () or (error: Error | null) and return void");
5547
+ }
5548
+ const param = cb.type.params[0];
5549
+ if (param !== undefined) {
5550
+ const def = param.kind === "union" ? lowerer.unions.get(param.unionId) : undefined;
5551
+ const valid = !!def && def.arms.length === 2 &&
5552
+ def.arms.some((arm) => arm.kind === "nullT") &&
5553
+ def.arms.some((arm) => arm.kind === "object" && arm.className === "%Error");
5554
+ if (!valid) {
5555
+ lowerer.unsupported("SC1090", node, `send callbacks whose parameter is not 'Error | null' (got '${lowerer.fmt(param)}')`);
5556
+ }
5557
+ }
5558
+ return cb;
5559
+ }
5560
+ function lowerProcessIpcSend(lowerer, call) {
5561
+ const loc = locOf(call);
5562
+ if (call.arguments.length < 1 || call.arguments.length > 2 || call.arguments.some(ts.isSpreadElement)) {
5563
+ lowerer.noLowering(`process.send with ${call.arguments.length} arguments`, call, "send(message[, callback]) is supported; handle transfer and options are not");
5564
+ }
5565
+ const json = ipcJsonMessage(lowerer, call.arguments[0], loc);
5566
+ if (call.arguments[1] === undefined) {
5567
+ return { kind: "libCall", fn: "process.send", args: [json], type: BOOL, loc };
5568
+ }
5569
+ const callback = ipcSendCallback(lowerer, call.arguments[1]);
5570
+ return { kind: "libCall", fn: "process.sendCb", args: [json, callback], type: BOOL, loc };
5571
+ }
4589
5572
  export function lowerChildMethodCall(lowerer, call, access) {
4590
5573
  if (call.questionDotToken || access.questionDotToken)
4591
5574
  return null;
@@ -4595,20 +5578,63 @@ export function lowerChildMethodCall(lowerer, call, access) {
4595
5578
  return null;
4596
5579
  const name = access.name.text;
4597
5580
  const loc = locOf(call);
4598
- if (name === "on" && call.arguments.length === 2) {
5581
+ if (name === "send") {
5582
+ if (call.arguments.length < 1 || call.arguments.length > 2 || call.arguments.some(ts.isSpreadElement)) {
5583
+ lowerer.noLowering(`child.send with ${call.arguments.length} arguments`, call, "send(message[, callback]) is supported; handle transfer and options are not");
5584
+ }
5585
+ const receiver = lowerer.lowerExpr(access.expression);
5586
+ const json = ipcJsonMessage(lowerer, call.arguments[0], loc);
5587
+ if (call.arguments[1] === undefined) {
5588
+ return { kind: "libCall", fn: "child.send", args: [receiver, json], type: BOOL, loc };
5589
+ }
5590
+ const callback = ipcSendCallback(lowerer, call.arguments[1]);
5591
+ return { kind: "libCall", fn: "child.sendCb", args: [receiver, json, callback], type: BOOL, loc };
5592
+ }
5593
+ if (name === "disconnect" && call.arguments.length === 0) {
5594
+ if (!ts.isExpressionStatement(call.parent)) {
5595
+ lowerer.unsupported("SC1090", call, "using the result of child.disconnect() (call it as its own statement)");
5596
+ }
5597
+ return {
5598
+ kind: "libCall",
5599
+ fn: "child.disconnect",
5600
+ args: [lowerer.lowerExpr(access.expression)],
5601
+ type: VOID,
5602
+ loc,
5603
+ };
5604
+ }
5605
+ if ((name === "on" || name === "once") && call.arguments.length === 2) {
4599
5606
  const evT = lowerer.typeOf(call.arguments[0]);
4600
5607
  const event = evT.isStringLiteralType() ? evT.value : null;
4601
- if (event !== "exit" && event !== "error") {
4602
- lowerer.noLowering(`child.on(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call.arguments[0], '"exit" and "error" are the supported child events (as literals)');
5608
+ if (event !== "exit" && event !== "close" && event !== "error" && event !== "message" && event !== "disconnect") {
5609
+ lowerer.noLowering(`child.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call.arguments[0], '"exit", "close", "error", "message", and "disconnect" are the supported child events (as literals)');
4603
5610
  }
4604
5611
  if (!ts.isExpressionStatement(call.parent)) {
4605
- lowerer.unsupported("SC1090", call, "chaining child.on(...) (the result is void here — register each listener as its own statement)");
5612
+ lowerer.unsupported("SC1090", call, `chaining child.${name}(...) (the result is void here — register each listener as its own statement)`);
4606
5613
  }
4607
5614
  const receiver = lowerer.lowerExpr(access.expression);
5615
+ const once = { kind: "boolLit", value: name === "once", type: BOOL, loc };
5616
+ if (event === "message") {
5617
+ return {
5618
+ kind: "libCall",
5619
+ fn: "child.onMessage",
5620
+ args: [receiver, ipcMessageListener(lowerer, call.arguments[1]), once],
5621
+ type: VOID,
5622
+ loc,
5623
+ };
5624
+ }
5625
+ if (event === "disconnect") {
5626
+ return {
5627
+ kind: "libCall",
5628
+ fn: "child.onDisconnect",
5629
+ args: [receiver, ipcDisconnectListener(lowerer, call.arguments[1]), once],
5630
+ type: VOID,
5631
+ loc,
5632
+ };
5633
+ }
4608
5634
  const cb = lowerer.lowerExpr(call.arguments[1]);
4609
- if (cb.type.kind !== "func" || cb.type.params.length > (event === "exit" ? 2 : 1)) {
4610
- lowerer.unsupported("SC1090", call.arguments[1], event === "exit"
4611
- ? "exit listeners with more than two parameters (use (code, signal), (code), or ())"
5635
+ if (cb.type.kind !== "func" || cb.type.params.length > (event === "error" ? 1 : 2)) {
5636
+ lowerer.unsupported("SC1090", call.arguments[1], event !== "error"
5637
+ ? `${event} listeners with more than two parameters (use (code, signal), (code), or ())`
4612
5638
  : "error listeners with more than one parameter (use (err) or ())");
4613
5639
  }
4614
5640
  if (cb.type.ret.kind !== "void") {
@@ -4618,7 +5644,7 @@ export function lowerChildMethodCall(lowerer, call, access) {
4618
5644
  lowerer.unsupported("SC1090", call.arguments[1], "listeners returning a value (make the callback body a block, or return nothing)");
4619
5645
  }
4620
5646
  const param = cb.type.params[0];
4621
- if (event === "exit") {
5647
+ if (event === "exit" || event === "close") {
4622
5648
  const armsOk = param === undefined ||
4623
5649
  (param.kind === "union" &&
4624
5650
  (() => {
@@ -4628,7 +5654,7 @@ export function lowerChildMethodCall(lowerer, call, access) {
4628
5654
  def.arms[1].kind === "nullT");
4629
5655
  })());
4630
5656
  if (!armsOk) {
4631
- lowerer.unsupported("SC1090", call.arguments[1], `exit listeners whose parameter is not 'number | null' (got '${lowerer.fmt(param)}')`);
5657
+ lowerer.unsupported("SC1090", call.arguments[1], `${event} listeners whose parameter is not 'number | null' (got '${lowerer.fmt(param)}')`);
4632
5658
  }
4633
5659
  // The optional SECOND parameter is Node's signal: the terminating
4634
5660
  // signal's name as `Signals | null` — a string | null union here
@@ -4643,9 +5669,9 @@ export function lowerChildMethodCall(lowerer, call, access) {
4643
5669
  def.arms.some((a) => a.kind === "nullT"));
4644
5670
  })());
4645
5671
  if (!sigOk) {
4646
- lowerer.unsupported("SC1090", call.arguments[1], `exit listeners whose signal parameter is not 'Signals | null' (got '${lowerer.fmt(sigParam)}')`);
5672
+ lowerer.unsupported("SC1090", call.arguments[1], `${event} listeners whose signal parameter is not 'Signals | null' (got '${lowerer.fmt(sigParam)}')`);
4647
5673
  }
4648
- return { kind: "libCall", fn: "child.onExit", args: [receiver, cb], type: VOID, loc };
5674
+ return { kind: "libCall", fn: event === "close" ? "child.onClose" : "child.onExit", args: [receiver, cb], type: VOID, loc };
4649
5675
  }
4650
5676
  if (param !== undefined && !(param.kind === "object" && param.className === "%Error")) {
4651
5677
  lowerer.unsupported("SC1090", call.arguments[1], `error listeners whose parameter is not 'Error' (got '${lowerer.fmt(param)}')`);
@@ -4684,7 +5710,7 @@ export function lowerChildMethodCall(lowerer, call, access) {
4684
5710
  const receiver = lowerer.lowerExpr(access.expression);
4685
5711
  return { kind: "libCall", fn: "child.unref", args: [receiver], type: VOID, loc };
4686
5712
  }
4687
- lowerer.noLowering(`ChildProcess.${name}`, call, "on(\"exit\" | \"error\", cb), pid, exitCode, killed, kill(signal?), and unref() are the supported ChildProcess members", lowerer.checker.getSymbolAtLocation(access.name));
5713
+ lowerer.noLowering(`ChildProcess.${name}`, call, "send(), connected, disconnect(), on/once(\"message\" | \"disconnect\" | \"exit\" | \"close\" | \"error\", cb), pid, exitCode, killed, kill(signal?), and unref() are supported", lowerer.checker.getSymbolAtLocation(access.name));
4688
5714
  }
4689
5715
  /** Method calls on piped child-output stream receivers (child.stdout /
4690
5716
  * child.stderr — the childStream kind): on/once("data" | "end").
@@ -4706,6 +5732,11 @@ export function lowerChildStreamMethodCall(lowerer, call, access) {
4706
5732
  return null;
4707
5733
  const name = access.name.text;
4708
5734
  const loc = locOf(call);
5735
+ if (name === "setEncoding" && call.arguments.length === 1) {
5736
+ const receiver = lowerer.lowerExprExpecting(access.expression, CHILDSTREAM_T);
5737
+ const enc = bufEncoding(lowerer, "child stream setEncoding", call.arguments[0]);
5738
+ return { kind: "libCall", fn: "stream.childSetEncoding", args: [receiver, strLit(enc, loc)], type: CHILDSTREAM_T, loc };
5739
+ }
4709
5740
  if ((name === "on" || name === "once") && call.arguments.length === 2) {
4710
5741
  const evT = lowerer.typeOf(call.arguments[0]);
4711
5742
  const event = evT.isStringLiteralType() ? evT.value : null;
@@ -4734,12 +5765,73 @@ export function lowerChildStreamMethodCall(lowerer, call, access) {
4734
5765
  const def = lowerer.unions.get(p.unionId);
4735
5766
  return !!def && def.arms.some((a) => a.kind === "bytes" && a.elem === "u8");
4736
5767
  };
4737
- if (param !== undefined && !(param.kind === "bytes" && param.elem === "u8") && !unionOk(param)) {
4738
- lowerer.unsupported("SC1090", call.arguments[1], `data listeners whose parameter is not 'Buffer' (or a Buffer-armed union; got '${lowerer.fmt(param)}')`);
5768
+ if (param !== undefined && param.kind !== "string" && !(param.kind === "bytes" && param.elem === "u8") && !unionOk(param)) {
5769
+ lowerer.unsupported("SC1090", call.arguments[1], `data listeners whose parameter is not 'Buffer', 'string', or a Buffer-armed union (got '${lowerer.fmt(param)}')`);
5770
+ }
5771
+ return { kind: "libCall", fn: param?.kind === "string" ? "stream.onDataStr" : "stream.onData", args: [receiver, cb, once], type: VOID, loc };
5772
+ }
5773
+ lowerer.noLowering(`ReadableStream.${name}`, call, 'setEncoding(encoding) and on/once("data" | "end", cb) are the supported child-stream members', lowerer.checker.getSymbolAtLocation(access.name));
5774
+ }
5775
+ /** Method calls on a piped child stdin writer. Writes copy one string or
5776
+ * Uint8Array into the nonblocking runtime queue and return the backpressure
5777
+ * signal. end()/destroy() are statement-only, as are drain/finish/error
5778
+ * listener registrations; unsupported callbacks/encodings stay fenced. */
5779
+ export function lowerChildWriterMethodCall(lowerer, call, access) {
5780
+ if (lowerer.chainBlocked(call, access))
5781
+ return null;
5782
+ if (lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind !== "childWriter")
5783
+ return null;
5784
+ if (!lowerer.isStdlibMember(access))
5785
+ return null;
5786
+ const name = access.name.text;
5787
+ const loc = locOf(call);
5788
+ if (name === "write") {
5789
+ if (call.arguments.length !== 1 || call.arguments.some(ts.isSpreadElement)) {
5790
+ lowerer.noLowering(`child stdin write with ${call.arguments.length} arguments`, call, "write(string | Uint8Array) with no encoding or callback is supported");
5791
+ }
5792
+ const receiver = lowerer.lowerExprExpecting(access.expression, CHILDWRITER_T);
5793
+ const data = lowerer.lowerExpr(call.arguments[0]);
5794
+ if (data.type.kind === "string") {
5795
+ return { kind: "libCall", fn: "writer.writeString", args: [receiver, data], type: BOOL, loc };
5796
+ }
5797
+ if (data.type.kind === "bytes" && data.type.elem === "u8") {
5798
+ return { kind: "libCall", fn: "writer.writeBytes", args: [receiver, data], type: BOOL, loc };
5799
+ }
5800
+ lowerer.noLowering(`child stdin write of '${lowerer.fmt(data.type)}'`, call.arguments[0], "write one string, Buffer, or Uint8Array value (narrow unions first)");
5801
+ }
5802
+ if ((name === "end" || name === "destroy") && call.arguments.length === 0) {
5803
+ if (!ts.isExpressionStatement(call.parent)) {
5804
+ lowerer.unsupported("SC1090", call, `chaining child.stdin.${name}() (call it as its own statement)`);
5805
+ }
5806
+ const receiver = lowerer.lowerExprExpecting(access.expression, CHILDWRITER_T);
5807
+ return { kind: "libCall", fn: name === "end" ? "writer.end" : "writer.destroy", args: [receiver], type: VOID, loc };
5808
+ }
5809
+ if ((name === "on" || name === "once") && call.arguments.length === 2) {
5810
+ const eventType = lowerer.typeOf(call.arguments[0]);
5811
+ const event = eventType.isStringLiteralType() ? eventType.value : null;
5812
+ if (event !== "drain" && event !== "finish" && event !== "error") {
5813
+ lowerer.noLowering(`child stdin ${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call.arguments[0], '"drain", "finish", and "error" are the supported child stdin events');
5814
+ }
5815
+ if (!ts.isExpressionStatement(call.parent)) {
5816
+ lowerer.unsupported("SC1090", call, "chaining child stdin listener registration");
4739
5817
  }
4740
- return { kind: "libCall", fn: "stream.onData", args: [receiver, cb, once], type: VOID, loc };
5818
+ const receiver = lowerer.lowerExprExpecting(access.expression, CHILDWRITER_T);
5819
+ const cb = lowerer.lowerExpr(call.arguments[1]);
5820
+ const maxParams = event === "error" ? 1 : 0;
5821
+ if (cb.type.kind !== "func" || cb.type.ret.kind !== "void" || cb.type.params.length > maxParams) {
5822
+ lowerer.unsupported("SC1090", call.arguments[1], event === "error"
5823
+ ? "error listeners with at most one Error parameter and no return value"
5824
+ : `${event} listeners with no parameters or return value`);
5825
+ }
5826
+ if (event === "error" && cb.type.params[0] !== undefined &&
5827
+ !(cb.type.params[0].kind === "object" && cb.type.params[0].className === "%Error")) {
5828
+ lowerer.unsupported("SC1090", call.arguments[1], "child stdin error listeners whose parameter is Error");
5829
+ }
5830
+ const once = boolLit(name === "once", loc);
5831
+ const fn = event === "drain" ? "writer.onDrain" : event === "finish" ? "writer.onFinish" : "writer.onError";
5832
+ return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc };
4741
5833
  }
4742
- lowerer.noLowering(`ReadableStream.${name}`, call, 'on/once("data" | "end", cb) are the supported child-stream members', lowerer.checker.getSymbolAtLocation(access.name));
5834
+ lowerer.noLowering(`Writable.${name}`, call, 'write(string | Uint8Array), end(), destroy(), and on/once("drain" | "finish" | "error", cb) are supported', lowerer.checker.getSymbolAtLocation(access.name));
4743
5835
  }
4744
5836
  /** Method calls on first-class process-stream receivers (procStream —
4745
5837
  * a WritableStream-typed value like prefixStream's `output` param):
@@ -4965,6 +6057,19 @@ export function lowerProcessStreamProperty(lowerer, expr) {
4965
6057
  let recv = expr.expression;
4966
6058
  while (ts.isParenthesizedExpression(recv) || ts.isAsExpression(recv) || ts.isTypeAssertion(recv))
4967
6059
  recv = recv.expression;
6060
+ // Browser-compatible packages commonly write
6061
+ // `(process.stdout || {}).isTTY`. Node's stream object is always
6062
+ // present and truthy, so the fallback cannot run; match the left
6063
+ // receiver exactly as the value-level always-truthy `||` lowering
6064
+ // does, preserving the native isatty/columns answer.
6065
+ if (ts.isBinaryExpression(recv) &&
6066
+ recv.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
6067
+ let left = recv.left;
6068
+ while (ts.isParenthesizedExpression(left) || ts.isAsExpression(left) || ts.isTypeAssertion(left))
6069
+ left = left.expression;
6070
+ if (ts.isPropertyAccessExpression(left))
6071
+ recv = left;
6072
+ }
4968
6073
  if (!ts.isPropertyAccessExpression(recv))
4969
6074
  return null;
4970
6075
  const stream = lowerer.stdlibGlobalMember(recv, "process");
@@ -5074,6 +6179,9 @@ export function lowerProcessProperty(lowerer, expr) {
5074
6179
  if (member === "argv") {
5075
6180
  return { kind: "libCall", fn: "process.argv", args: [], type: arrayOf(STRING), loc };
5076
6181
  }
6182
+ if (member === "connected") {
6183
+ return { kind: "libCall", fn: "process.connected", args: [], type: BOOL, loc };
6184
+ }
5077
6185
  // process.execArgv: the extra CLI arguments Node itself consumed — a
5078
6186
  // compiled binary consumed none, so the honest answer is a fresh [].
5079
6187
  if (member === "execArgv") {
@@ -5152,48 +6260,55 @@ export function envValueType(lowerer) {
5152
6260
  * @types/node, a user alias reshaping the result) fences honestly. Key
5153
6261
  * and row order follow getifaddrs enumeration — Node itself guarantees
5154
6262
  * no order (compare structurally). */
5155
- /** `fs.readdirSync(path, { withFileTypes: true })` — Dirent rows: name +
5156
- * parentPath (the path argument as given, Node's own rule) + the hidden
5157
- * %dtype entry kind (libuv's UV_DIRENT encoding; DT_UNKNOWN falls back
5158
- * to lstat, Node's getDirents rule). The options literal is checked
5159
- * member-by-member; the result type must be the interned Dirent record
5160
- * array from type-mapper.ts — anything else (encoding: 'buffer', a user alias
5161
- * reshaping Dirent) fences honestly. */
5162
- function lowerFsReaddirTypesCall(lowerer, call, loc) {
6263
+ /** `fs.readdirSync` / `fs.promises.readdir` with `{ withFileTypes: true }`
6264
+ * — Dirent rows: name + parentPath (the path argument as given, Node's
6265
+ * own rule) + the hidden %dtype entry kind (libuv's UV_DIRENT encoding;
6266
+ * DT_UNKNOWN falls back to lstat, Node's getDirents rule). The options
6267
+ * literal is checked member-by-member; the result type must carry the
6268
+ * interned Dirent record array from type-mapper.ts — anything else
6269
+ * (encoding: 'buffer', a user alias reshaping Dirent) fences honestly. */
6270
+ function lowerFsReaddirTypesCall(lowerer, call, loc, promiseForm) {
6271
+ const operation = promiseForm ? "fs.promises.readdir" : "readdirSync";
6272
+ const spelling = promiseForm ? "readdir" : "readdirSync";
5163
6273
  const optsNode = call.arguments[1];
5164
6274
  if (!ts.isObjectLiteralExpression(optsNode)) {
5165
- lowerer.noLowering("readdirSync with a non-literal options argument", optsNode, "pass the options inline so each member can be checked: readdirSync(path, { withFileTypes: true })");
6275
+ lowerer.noLowering(`${operation} with a non-literal options argument`, optsNode, `pass the options inline so each member can be checked: ${spelling}(path, { withFileTypes: true })`);
5166
6276
  }
5167
6277
  let sawWithFileTypes = false;
5168
6278
  for (const p of optsNode.properties) {
5169
6279
  const m = optionMember(p);
5170
6280
  if (!m) {
5171
- lowerer.noLowering("readdirSync with this options shape", p, "spreads and computed keys have no lowering — write each member inline");
6281
+ lowerer.noLowering(`${operation} with this options shape`, p, "spreads and computed keys have no lowering — write each member inline");
5172
6282
  }
5173
6283
  if (m.name === "withFileTypes") {
5174
6284
  const t = lowerer.typeOf(m.value);
5175
6285
  if (!(t.flags & ts.TypeFlags.BooleanLiteral) || lowerer.checker.typeToString(t) !== "true") {
5176
- lowerer.noLowering("readdirSync with a non-literal-true withFileTypes", m.value, "withFileTypes: true is the Dirent form; omit the options for plain names");
6286
+ lowerer.noLowering(`${operation} with a non-literal-true withFileTypes`, m.value, "withFileTypes: true is the Dirent form; omit the options for plain names");
5177
6287
  }
5178
6288
  sawWithFileTypes = true;
5179
6289
  }
5180
6290
  else if (m.name === "encoding") {
5181
6291
  const t = lowerer.typeOf(m.value);
5182
6292
  if (!t.isStringLiteralType() || (t.value !== "utf8" && t.value !== "utf-8")) {
5183
- lowerer.noLowering("readdirSync with a non-utf8 encoding", m.value, "names decode as utf8 (the default); encoding: 'buffer' has no lowering");
6293
+ lowerer.noLowering(`${operation} with a non-utf8 encoding`, m.value, "names decode as utf8 (the default); encoding: 'buffer' has no lowering");
5184
6294
  }
5185
6295
  }
5186
6296
  else {
5187
6297
  // The options-record stance: recursive (a documented knob with
5188
6298
  // no lowering) fences by name; undocumented keys drop like Node.
5189
- fenceOrDropOptionKey(lowerer, p, m.name, "readdirSync", FS_READDIR_DOCUMENTED_OPTIONS, "withFileTypes: true (and the default encoding) is the supported options surface — recursive listings want an explicit walk");
6299
+ fenceOrDropOptionKey(lowerer, p, m.name, operation, FS_READDIR_DOCUMENTED_OPTIONS, "withFileTypes: true (and the default encoding) is the supported options surface — recursive listings want an explicit walk");
5190
6300
  }
5191
6301
  }
5192
6302
  if (!sawWithFileTypes) {
5193
- lowerer.noLowering("readdirSync with 2 arguments", call, "readdirSync(path) lists names; readdirSync(path, { withFileTypes: true }) lists Dirents");
6303
+ lowerer.noLowering(`${operation} with 2 arguments`, call, `${spelling}(path) lists names; ${spelling}(path, { withFileTypes: true }) lists Dirents`);
5194
6304
  }
5195
- const fence = () => lowerer.noLowering("readdirSync(path, { withFileTypes: true }) where the result is not the Dirent array", call, "{ name, parentPath, isFile(), isDirectory(), isSymbolicLink() } rows are the supported result shape");
5196
- const result = lowerer.mapTypeOf(lowerer.typeOf(call));
6305
+ const fence = () => lowerer.noLowering(`${operation}(path, { withFileTypes: true }) where the result is not the Dirent array`, call, "{ name, parentPath, isFile(), isDirectory(), isSymbolicLink() } rows are the supported result shape");
6306
+ const callType = lowerer.mapTypeOf(lowerer.typeOf(call));
6307
+ if (!callType)
6308
+ fence();
6309
+ const result = promiseForm
6310
+ ? callType.kind === "promise" ? callType.inner : fence()
6311
+ : callType;
5197
6312
  if (result?.kind !== "array" || result.elem.kind !== "record")
5198
6313
  fence();
5199
6314
  const shape = lowerer.shapes.get(result.elem.shapeId);
@@ -5205,7 +6320,13 @@ function lowerFsReaddirTypesCall(lowerer, call, loc) {
5205
6320
  fence();
5206
6321
  }
5207
6322
  const path = lowerer.lowerExprExpecting(call.arguments[0], STRING);
5208
- return { kind: "libCall", fn: "fs.readdirTypesSync", args: [path], type: result, loc };
6323
+ return {
6324
+ kind: "libCall",
6325
+ fn: promiseForm ? "fsp.readdirTypes" : "fs.readdirTypesSync",
6326
+ args: [path],
6327
+ type: callType,
6328
+ loc,
6329
+ };
5209
6330
  }
5210
6331
  /** `d.isFile()` / `d.isDirectory()` / `d.isSymbolicLink()` on a Dirent-
5211
6332
  * typed receiver (the interned record — provenance via the checker's
@@ -5539,13 +6660,23 @@ export function lowerProcessEnvGet(lowerer, expr) {
5539
6660
  }
5540
6661
  /** `process.exit(code)` / `process.cwd()` → libCall. The fallback
5541
6662
  * declaration makes exit's code required; @types/node declares it
5542
- * optional, and a bare `process.exit()` lowers as exit(0) — exactly
5543
- * Node's behavior when process.exitCode was never set (setting exitCode
5544
- * is fenced like every other unsupported process member, so "never set"
5545
- * always holds in a compiled program). */
6663
+ * optional. A bare `process.exit()` uses the current exitCode, or zero
6664
+ * when unset. */
5546
6665
  export function lowerProcessMethodCall(lowerer, call, access) {
5547
6666
  if (call.questionDotToken)
5548
6667
  return null;
6668
+ const directProcessMember = lowerer.stdlibGlobalMember(access, "process");
6669
+ if (directProcessMember === "send")
6670
+ return lowerProcessIpcSend(lowerer, call);
6671
+ if (directProcessMember === "disconnect") {
6672
+ if (call.arguments.length !== 0) {
6673
+ lowerer.noLowering(`process.disconnect with ${call.arguments.length} arguments`, call);
6674
+ }
6675
+ if (!ts.isExpressionStatement(call.parent)) {
6676
+ lowerer.unsupported("SC1090", call, "using the result of process.disconnect() (call it as its own statement)");
6677
+ }
6678
+ return { kind: "libCall", fn: "process.disconnect", args: [], type: VOID, loc: locOf(call) };
6679
+ }
5549
6680
  // process.stdout.write(s[, encoding][, callback]) and stderr's twin:
5550
6681
  // raw bytes, no newline or formatting. stdout shares console.log's
5551
6682
  // promptly-submitted stream, preserving source order. Node's boolean
@@ -5806,6 +6937,21 @@ export function lowerProcessMethodCall(lowerer, call, access) {
5806
6937
  }
5807
6938
  const evT = lowerer.typeOf(call.arguments[0]);
5808
6939
  const event = evT.isStringLiteralType() ? evT.value : null;
6940
+ if ((event === "message" || event === "disconnect") && !isOff) {
6941
+ if (!ts.isExpressionStatement(call.parent)) {
6942
+ lowerer.unsupported("SC1090", call, "chaining process IPC listener registration (register each listener as its own statement)");
6943
+ }
6944
+ const callback = event === "message"
6945
+ ? ipcMessageListener(lowerer, call.arguments[1])
6946
+ : ipcDisconnectListener(lowerer, call.arguments[1]);
6947
+ return {
6948
+ kind: "libCall",
6949
+ fn: event === "message" ? "process.onMessage" : "process.onDisconnect",
6950
+ args: [callback, boolLit(member === "once", loc)],
6951
+ type: VOID,
6952
+ loc,
6953
+ };
6954
+ }
5809
6955
  // own(), not a bare index: the key is a USER-written event name,
5810
6956
  // and `{ SIGINT: 2 }["__proto__"]` answers Object.prototype — an
5811
6957
  // object flowed into a numLit and emitted itself into the C
@@ -5848,7 +6994,7 @@ export function lowerProcessMethodCall(lowerer, call, access) {
5848
6994
  };
5849
6995
  }
5850
6996
  if (signo === undefined && event !== "exit") {
5851
- lowerer.noLowering(`process.${member}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call.arguments[0], '"SIGINT", "SIGTERM", "exit", "warning", "unhandledRejection", and "rejectionHandled" are the supported process events (as literals)');
6997
+ lowerer.noLowering(`process.${member}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call.arguments[0], '"message", "disconnect", "SIGINT", "SIGTERM", "exit", "warning", "unhandledRejection", and "rejectionHandled" are the supported process events (as literals)');
5852
6998
  }
5853
6999
  if (!ts.isExpressionStatement(call.parent)) {
5854
7000
  lowerer.unsupported("SC1090", call, "chaining process listener registration (the result is void here — register each listener as its own statement)");
@@ -6156,7 +7302,7 @@ export function lowerProcessMethodCall(lowerer, call, access) {
6156
7302
  const arg = call.arguments[0];
6157
7303
  const code = arg !== undefined
6158
7304
  ? lowerer.lowerExprExpecting(arg, F64)
6159
- : { kind: "numLit", value: 0, type: F64, loc };
7305
+ : { kind: "libCall", fn: "process.currentExitCode", args: [], type: F64, loc };
6160
7306
  return { kind: "libCall", fn: "process.exit", args: [code], type: VOID, loc };
6161
7307
  }
6162
7308
  return null; // process.argv(...) etc. are tsc errors before lowering
@@ -6268,6 +7414,16 @@ export function lowerDateCall(lowerer, call, access) {
6268
7414
  }
6269
7415
  return { kind: "libCall", fn: "date.now", args: [], type: F64, loc };
6270
7416
  }
7417
+ // Date.parse uses the same bounded date-string parser as
7418
+ // new Date(dateString).getTime(); keep their NaN and TimeClip behavior
7419
+ // identical for the ISO timestamps used by CLI applications.
7420
+ if (lowerer.stdlibGlobalMember(access, "Date") === "parse") {
7421
+ if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0])) {
7422
+ lowerer.noLowering(`Date.parse with ${call.arguments.length} arguments`, call);
7423
+ }
7424
+ const input = lowerer.lowerExprExpecting(call.arguments[0], STRING);
7425
+ return { kind: "libCall", fn: "date.parse", args: [input], type: F64, loc };
7426
+ }
6271
7427
  // Date.UTC(year[, month[, date[, hours[, minutes[, seconds[, ms]]]]]]):
6272
7428
  // a pure function of its numbers — the runtime's MakeDay/MakeTime/
6273
7429
  // TimeClip. Omitted trailing arguments complete with the spec's
@@ -7107,6 +8263,8 @@ export function lowerProcessOptionalMethodCall(lowerer, expr) {
7107
8263
  if (!ts.isPropertyAccessExpression(expr.expression))
7108
8264
  return null;
7109
8265
  const member = lowerer.stdlibGlobalMember(expr.expression, "process");
8266
+ if (member === "send")
8267
+ return lowerProcessIpcSend(lowerer, expr);
7110
8268
  if (member !== "getuid" && member !== "getgid")
7111
8269
  return null;
7112
8270
  if (expr.arguments.length !== 0) {