@scriptc/compiler 0.1.3 → 0.1.5

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 (217) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +275 -62
  2. package/dist/backend/c/async.d.ts +25 -0
  3. package/dist/backend/c/async.js +167 -0
  4. package/dist/backend/c/async.js.map +1 -1
  5. package/dist/backend/c/c-emitter.d.ts +15 -1
  6. package/dist/backend/c/c-emitter.js +36 -11
  7. package/dist/backend/c/c-emitter.js.map +1 -1
  8. package/dist/backend/c/exprs.d.ts +2 -2
  9. package/dist/backend/c/exprs.js +556 -71
  10. package/dist/backend/c/exprs.js.map +1 -1
  11. package/dist/backend/c/stmts.js +5 -3
  12. package/dist/backend/c/stmts.js.map +1 -1
  13. package/dist/backend/c/types.d.ts +2 -3
  14. package/dist/backend/c/types.js +10 -1
  15. package/dist/backend/c/types.js.map +1 -1
  16. package/dist/backend/c/walkers.js +1 -1
  17. package/dist/backend/c/walkers.js.map +1 -1
  18. package/dist/backend/link-plan.d.ts +3 -1
  19. package/dist/backend/link-plan.js +3 -1
  20. package/dist/backend/link-plan.js.map +1 -1
  21. package/dist/backend/llvm/dyn.js +1 -1
  22. package/dist/backend/llvm/dyn.js.map +1 -1
  23. package/dist/backend/llvm/emitter.js +42 -12
  24. package/dist/backend/llvm/emitter.js.map +1 -1
  25. package/dist/backend/llvm/expr-bytes.d.ts +1 -2
  26. package/dist/backend/llvm/expr-bytes.js +28 -23
  27. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  28. package/dist/backend/llvm/expr-callbacks.d.ts +9 -0
  29. package/dist/backend/llvm/expr-callbacks.js +125 -0
  30. package/dist/backend/llvm/expr-callbacks.js.map +1 -1
  31. package/dist/backend/llvm/expr-containers.d.ts +1 -0
  32. package/dist/backend/llvm/expr-containers.js +32 -4
  33. package/dist/backend/llvm/expr-containers.js.map +1 -1
  34. package/dist/backend/llvm/expr-context.d.ts +19 -3
  35. package/dist/backend/llvm/expr-dispatch.js +1 -0
  36. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  37. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  38. package/dist/backend/llvm/expr-primitives.js +49 -13
  39. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  40. package/dist/backend/llvm/expr-primitives.test.d.ts +1 -0
  41. package/dist/backend/llvm/expr-primitives.test.js +38 -0
  42. package/dist/backend/llvm/expr-primitives.test.js.map +1 -0
  43. package/dist/backend/llvm/expr-stream-callbacks.d.ts +3 -0
  44. package/dist/backend/llvm/expr-stream-callbacks.js +69 -0
  45. package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
  46. package/dist/backend/llvm/lib-filesystem.js +80 -10
  47. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  48. package/dist/backend/llvm/lib-network.js +69 -10
  49. package/dist/backend/llvm/lib-network.js.map +1 -1
  50. package/dist/backend/llvm/lib-process.js +156 -6
  51. package/dist/backend/llvm/lib-process.js.map +1 -1
  52. package/dist/backend/llvm/lib-shared.js +100 -0
  53. package/dist/backend/llvm/lib-shared.js.map +1 -1
  54. package/dist/backend/llvm/lib-stream.js +23 -0
  55. package/dist/backend/llvm/lib-stream.js.map +1 -1
  56. package/dist/backend/native-toolchain.d.ts +5 -0
  57. package/dist/backend/native-toolchain.js +4 -1
  58. package/dist/backend/native-toolchain.js.map +1 -1
  59. package/dist/backend/runtime-pack.test.js +28 -10
  60. package/dist/backend/runtime-pack.test.js.map +1 -1
  61. package/dist/backend/targets.d.ts +7 -0
  62. package/dist/backend/targets.js +26 -0
  63. package/dist/backend/targets.js.map +1 -1
  64. package/dist/backend/targets.test.js +8 -1
  65. package/dist/backend/targets.test.js.map +1 -1
  66. package/dist/coverage/surface-manifest.js +14 -4
  67. package/dist/coverage/surface-manifest.js.map +1 -1
  68. package/dist/diagnostics/diagnostic.js +1 -1
  69. package/dist/diagnostics/diagnostic.js.map +1 -1
  70. package/dist/executable/executable-cache.d.ts +4 -0
  71. package/dist/executable/executable-cache.js +6 -1
  72. package/dist/executable/executable-cache.js.map +1 -1
  73. package/dist/executable/executable-cache.test.js +3 -3
  74. package/dist/executable/executable-cache.test.js.map +1 -1
  75. package/dist/frontend/fork-target.d.ts +17 -0
  76. package/dist/frontend/fork-target.js +256 -0
  77. package/dist/frontend/fork-target.js.map +1 -0
  78. package/dist/frontend/lowering/abstract-equality.d.ts +6 -0
  79. package/dist/frontend/lowering/abstract-equality.js +250 -0
  80. package/dist/frontend/lowering/abstract-equality.js.map +1 -0
  81. package/dist/frontend/lowering/containers/bytes.d.ts +1 -1
  82. package/dist/frontend/lowering/containers/bytes.js +6 -5
  83. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  84. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +7 -6
  85. package/dist/frontend/lowering/containers/string-and-regexp.js +237 -28
  86. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  87. package/dist/frontend/lowering/expressions/object-literals.js +8 -0
  88. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  89. package/dist/frontend/lowering/lower-assert.js +71 -2
  90. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  91. package/dist/frontend/lowering/lower-builtins.d.ts +54 -28
  92. package/dist/frontend/lowering/lower-builtins.js +957 -116
  93. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  94. package/dist/frontend/lowering/lower-calls.d.ts +32 -23
  95. package/dist/frontend/lowering/lower-calls.js +835 -206
  96. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  97. package/dist/frontend/lowering/lower-classes.js +41 -10
  98. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  99. package/dist/frontend/lowering/lower-containers.d.ts +7 -9
  100. package/dist/frontend/lowering/lower-containers.js +293 -51
  101. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  102. package/dist/frontend/lowering/lower-event-emitter.js +212 -21
  103. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  104. package/dist/frontend/lowering/lower-exprs.d.ts +12 -0
  105. package/dist/frontend/lowering/lower-exprs.js +263 -93
  106. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  107. package/dist/frontend/lowering/lower-inspect.d.ts +4 -0
  108. package/dist/frontend/lowering/lower-inspect.js +1 -1
  109. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  110. package/dist/frontend/lowering/lower-island.d.ts +6 -3
  111. package/dist/frontend/lowering/lower-island.js +182 -12
  112. package/dist/frontend/lowering/lower-island.js.map +1 -1
  113. package/dist/frontend/lowering/lower-modules.d.ts +6 -0
  114. package/dist/frontend/lowering/lower-modules.js +184 -38
  115. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  116. package/dist/frontend/lowering/lower-namespaces.js +7 -0
  117. package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
  118. package/dist/frontend/lowering/lower-server.d.ts +3 -5
  119. package/dist/frontend/lowering/lower-server.js +413 -54
  120. package/dist/frontend/lowering/lower-server.js.map +1 -1
  121. package/dist/frontend/lowering/lower-stmts.js +122 -19
  122. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  123. package/dist/frontend/lowering/lower-stream.d.ts +3 -0
  124. package/dist/frontend/lowering/lower-stream.js +7 -0
  125. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  126. package/dist/frontend/lowering/lowerer.d.ts +86 -9
  127. package/dist/frontend/lowering/lowerer.js +449 -74
  128. package/dist/frontend/lowering/lowerer.js.map +1 -1
  129. package/dist/frontend/lowering/optional-arguments.d.ts +9 -1
  130. package/dist/frontend/lowering/optional-arguments.js +67 -1
  131. package/dist/frontend/lowering/optional-arguments.js.map +1 -1
  132. package/dist/frontend/lowering/pairs-snapshot.d.ts +3 -1
  133. package/dist/frontend/lowering/pairs-snapshot.js +12 -4
  134. package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
  135. package/dist/frontend/lowering/surfaces.d.ts +34 -14
  136. package/dist/frontend/lowering/surfaces.js +177 -104
  137. package/dist/frontend/lowering/surfaces.js.map +1 -1
  138. package/dist/frontend/npm-static-declarations.d.ts +4 -0
  139. package/dist/frontend/npm-static-declarations.js +56 -0
  140. package/dist/frontend/npm-static-declarations.js.map +1 -1
  141. package/dist/frontend/npm-static-declarations.test.js +23 -2
  142. package/dist/frontend/npm-static-declarations.test.js.map +1 -1
  143. package/dist/frontend/npm-static-prune.d.ts +6 -0
  144. package/dist/frontend/npm-static-prune.js +203 -0
  145. package/dist/frontend/npm-static-prune.js.map +1 -0
  146. package/dist/frontend/npm-static-prune.test.d.ts +1 -0
  147. package/dist/frontend/npm-static-prune.test.js +48 -0
  148. package/dist/frontend/npm-static-prune.test.js.map +1 -0
  149. package/dist/frontend/npm-static.js +6 -5
  150. package/dist/frontend/npm-static.js.map +1 -1
  151. package/dist/frontend/npm.d.ts +15 -3
  152. package/dist/frontend/npm.js +87 -14
  153. package/dist/frontend/npm.js.map +1 -1
  154. package/dist/frontend/npm.test.js +76 -9
  155. package/dist/frontend/npm.test.js.map +1 -1
  156. package/dist/frontend/program.js +107 -14
  157. package/dist/frontend/program.js.map +1 -1
  158. package/dist/frontend/provenance.js +8 -3
  159. package/dist/frontend/provenance.js.map +1 -1
  160. package/dist/frontend/resolve.d.ts +6 -0
  161. package/dist/frontend/resolve.js +7 -4
  162. package/dist/frontend/resolve.js.map +1 -1
  163. package/dist/frontend/type-mapper.d.ts +5 -0
  164. package/dist/frontend/type-mapper.js +80 -29
  165. package/dist/frontend/type-mapper.js.map +1 -1
  166. package/dist/index.d.ts +8 -3
  167. package/dist/index.js +34 -41
  168. package/dist/index.js.map +1 -1
  169. package/dist/ir/analysis.d.ts +5 -3
  170. package/dist/ir/analysis.js +25 -11
  171. package/dist/ir/analysis.js.map +1 -1
  172. package/dist/ir/analysis.test.js +50 -2
  173. package/dist/ir/analysis.test.js.map +1 -1
  174. package/dist/ir/constant-tables.d.ts +17 -0
  175. package/dist/ir/constant-tables.js +99 -0
  176. package/dist/ir/constant-tables.js.map +1 -0
  177. package/dist/ir/constant-tables.test.d.ts +1 -0
  178. package/dist/ir/constant-tables.test.js +78 -0
  179. package/dist/ir/constant-tables.test.js.map +1 -0
  180. package/dist/ir/integer-ranges.d.ts +13 -0
  181. package/dist/ir/integer-ranges.js +122 -0
  182. package/dist/ir/integer-ranges.js.map +1 -0
  183. package/dist/ir/integer-ranges.test.d.ts +1 -0
  184. package/dist/ir/integer-ranges.test.js +63 -0
  185. package/dist/ir/integer-ranges.test.js.map +1 -0
  186. package/dist/ir/ir.d.ts +130 -76
  187. package/dist/ir/ir.js +69 -4
  188. package/dist/ir/ir.js.map +1 -1
  189. package/dist/ir/ir.test.js +20 -1
  190. package/dist/ir/ir.test.js.map +1 -1
  191. package/dist/ir/scalar-records.d.ts +8 -0
  192. package/dist/ir/scalar-records.js +216 -0
  193. package/dist/ir/scalar-records.js.map +1 -0
  194. package/dist/ir/scalar-records.test.d.ts +1 -0
  195. package/dist/ir/scalar-records.test.js +142 -0
  196. package/dist/ir/scalar-records.test.js.map +1 -0
  197. package/dist/ir/serialize.d.ts +1 -1
  198. package/dist/ir/serialize.js +1 -1
  199. package/dist/ir/serialize.js.map +1 -1
  200. package/dist/ir/validate.js +256 -20
  201. package/dist/ir/validate.js.map +1 -1
  202. package/dist/ir/validate.test.d.ts +1 -0
  203. package/dist/ir/validate.test.js +31 -0
  204. package/dist/ir/validate.test.js.map +1 -0
  205. package/dist/library/cache-primitives.d.ts +1 -1
  206. package/dist/library/cache-primitives.js +3 -3
  207. package/dist/library/cache-primitives.js.map +1 -1
  208. package/dist/library/fence-eval.js +11 -4
  209. package/dist/library/fence-eval.js.map +1 -1
  210. package/dist/library/int-infer.test.js +4 -4
  211. package/dist/library/int-infer.test.js.map +1 -1
  212. package/dist/library/library-cache.js +0 -1
  213. package/dist/library/library-cache.js.map +1 -1
  214. package/dist/library/library-cache.test.js +8 -8
  215. package/dist/library/library-cache.test.js.map +1 -1
  216. package/package.json +17 -17
  217. package/surface-manifest.json +186 -79
@@ -7,25 +7,28 @@ 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";
21
- import { defaultAfterUndefined, lowerStaticallyUndefinedArgument } from "./optional-arguments.js";
21
+ import { defaultAfterUndefined, lowerOptionalArgument, lowerStaticallyUndefinedArgument, lowerStringSearchArgument } from "./optional-arguments.js";
22
22
  import { HTTP2_CONSTANTS } from "./http2-constants.js";
23
23
  import { CRYPTO_CIPHERS, CRYPTO_CONSTANTS, CRYPTO_CURVES, CRYPTO_HASHES } from "./crypto-tables.js";
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 { bufEncoding } from "./containers/bytes.js";
27
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
  }
@@ -2063,7 +2459,7 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2063
2459
  optsNode = second;
2064
2460
  }
2065
2461
  if (optsNode !== undefined && !ts.isObjectLiteralExpression(optsNode)) {
2066
- lowerer.noLowering("spawn with a non-literal options argument", optsNode, "pass stdio, detached, env, cwd, and windowsHide in an inline object literal");
2462
+ lowerer.noLowering("spawn with a non-literal options argument", optsNode, "pass stdio, detached, env, cwd, windowsHide, and shell in an inline object literal");
2067
2463
  }
2068
2464
  const emptyStr = { kind: "strLit", value: "", type: STRING, loc };
2069
2465
  // Per-slot stdio modes (scr_child.c: 0 ignore, 1 inherit, 2 fd) and
@@ -2074,6 +2470,8 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2074
2470
  let outFd = numLit(0, loc);
2075
2471
  let errFd = numLit(0, loc);
2076
2472
  let detached = boolLit(false, loc);
2473
+ let shell = boolLit(false, loc);
2474
+ let shellEnabled = false;
2077
2475
  let hasEnv = boolLit(false, loc);
2078
2476
  let envPairs = { kind: "arrayLit", elems: [], type: arrayOf(STRING), loc };
2079
2477
  let cwd = emptyStr;
@@ -2194,8 +2592,21 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2194
2592
  case "windowsHide":
2195
2593
  lowerer.lowerExpr(m.value); // Node no-op on POSIX
2196
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;
2197
2608
  default:
2198
- 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");
2199
2610
  }
2200
2611
  }
2201
2612
  }
@@ -2203,6 +2614,12 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2203
2614
  inMode = outMode = errMode = 3;
2204
2615
  plain = false;
2205
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
+ }
2622
+ }
2206
2623
  const argv = lowerer.lowerChildArgsArg(argsNode, loc);
2207
2624
  if (plain) {
2208
2625
  return { kind: "libCall", fn: "cp.spawn", args: [cmd, argv], type: CHILD_T, loc };
@@ -2210,11 +2627,213 @@ export function lowerSpawnCall(lowerer, expr, loc) {
2210
2627
  return {
2211
2628
  kind: "libCall",
2212
2629
  fn: "cp.spawnOpts",
2213
- 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
+ ],
2214
2763
  type: CHILD_T,
2215
2764
  loc,
2216
2765
  };
2217
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
+ }
2218
2837
  /** `execFileSync(file, args?, options?)` / `execSync(command, options?)`
2219
2838
  * → the ONE cp.execSync libCall. execSync wraps the command in
2220
2839
  * `/bin/sh -c` (Node's shell semantics — a single command string, no
@@ -2714,13 +3333,10 @@ function execStdioModeHelper(lowerer, shapeId, stdioT, fd, loc) {
2714
3333
  });
2715
3334
  return name;
2716
3335
  }
2717
- /** `const execFileAsync = promisify(execFile)` — the ONE lowered
2718
- * util.promisify shape. Returns true (and registers the declared symbol
2719
- * so call sites lower and value uses fence) when `init` is a promisify
2720
- * call over a child_process.execFile import binding; a promisify call
2721
- * over anything else fences HERE with the supported-target hint (the
2722
- * declaration is where the target is visible). False for non-promisify
2723
- * 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. */
2724
3340
  export function isPromisifyCall(lowerer, init) {
2725
3341
  let e = init;
2726
3342
  while (ts.isParenthesizedExpression(e))
@@ -2734,7 +3350,38 @@ export function isPromisifyCall(lowerer, init) {
2734
3350
  return null;
2735
3351
  return e;
2736
3352
  }
2737
- 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) {
2738
3385
  if (!init)
2739
3386
  return false;
2740
3387
  // The OTHER special const-binding form this decl hook serves: the
@@ -2748,12 +3395,17 @@ export function promisifiedExecFileDecl(lowerer, nameNode, init) {
2748
3395
  return false;
2749
3396
  const argNode = e.arguments.length === 1 ? e.arguments[0] : null;
2750
3397
  const target = argNode && ts.isIdentifier(argNode) ? lowerer.builtinImportOf(argNode) : null;
2751
- if (!target || target.module !== "child_process" || target.member !== "execFile") {
2752
- 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");
2753
3405
  }
2754
3406
  const symbol = lowerer.checker.getSymbolAtLocation(nameNode);
2755
3407
  if (symbol)
2756
- lowerer.promisifiedExecFile.add(symbol);
3408
+ lowerer.staticCallables.set(symbol, projection);
2757
3409
  return true;
2758
3410
  }
2759
3411
  /** A call THROUGH a promisified-execFile binding:
@@ -3554,6 +4206,9 @@ export function lowerJsonMethodCall(lowerer, call, access) {
3554
4206
  }
3555
4207
  if (member === "stringify") {
3556
4208
  const indent = stringifySpaceIndent(lowerer, call);
4209
+ if (call.arguments.length === 0) {
4210
+ return lowerer.wrappedUndefined(lowerer.withUndefinedArm(STRING), loc);
4211
+ }
3557
4212
  const argNode = call.arguments[0];
3558
4213
  const value = lowerer.lowerExpr(argNode);
3559
4214
  const optionalString = lowerOptionalStringifyRoot(lowerer, value, indent, loc);
@@ -3862,18 +4517,18 @@ function cryptoInputBytes(lowerer, node, loc) {
3862
4517
  }
3863
4518
  lowerer.noLowering(`crypto byte input of '${lowerer.fmt(value.type)}' values`, node, "string and Buffer/Uint8Array values are supported");
3864
4519
  }
3865
- function cryptoBytesCallback(lowerer, node) {
4520
+ function errorFirstBytesCallback(lowerer, node, api) {
3866
4521
  let callback = lowerer.lowerExpr(node);
3867
4522
  if (callback.type.kind === "dyn") {
3868
4523
  callback = { kind: "dynCheck", value: callback, type: funcOf([DYN, DYN], VOID), loc: locOf(node) };
3869
4524
  }
3870
4525
  if (callback.type.kind !== "func" || callback.type.params.length > 2) {
3871
- lowerer.unsupported("SC1090", node, "crypto callbacks must accept at most (error, buffer)");
4526
+ lowerer.unsupported("SC1090", node, `${api} callbacks must accept at most (error, buffer)`);
3872
4527
  }
3873
4528
  const error = callback.type.params[0];
3874
4529
  if (error !== undefined && error.kind !== "dyn") {
3875
4530
  if (error.kind !== "union") {
3876
- lowerer.unsupported("SC1090", node, "crypto callback error parameters must be Error | null");
4531
+ lowerer.unsupported("SC1090", node, `${api} callback error parameters must be Error | null`);
3877
4532
  }
3878
4533
  const def = lowerer.unions.get(error.unionId);
3879
4534
  const valid = !!def &&
@@ -3881,11 +4536,11 @@ function cryptoBytesCallback(lowerer, node) {
3881
4536
  def.arms.some((arm) => arm.kind === "object" && arm.className === "%Error") &&
3882
4537
  def.arms.every((arm) => arm.kind === "nullT" || arm.kind === "undefinedT" || (arm.kind === "object" && arm.className === "%Error"));
3883
4538
  if (!valid)
3884
- lowerer.unsupported("SC1090", node, "crypto callback error parameters must be Error | null");
4539
+ lowerer.unsupported("SC1090", node, `${api} callback error parameters must be Error | null`);
3885
4540
  }
3886
4541
  const value = callback.type.params[1];
3887
4542
  if (value !== undefined && value.kind !== "dyn" && !(value.kind === "bytes" && value.elem === "u8")) {
3888
- lowerer.unsupported("SC1090", node, "crypto callback result parameters must be Buffer/Uint8Array values");
4543
+ lowerer.unsupported("SC1090", node, `${api} callback result parameters must be Buffer/Uint8Array values`);
3889
4544
  }
3890
4545
  return voidizedCallback(lowerer, callback, locOf(node));
3891
4546
  }
@@ -3909,7 +4564,7 @@ export function lowerCryptoModuleCall(lowerer, expr, bi, loc) {
3909
4564
  return {
3910
4565
  kind: "libCall",
3911
4566
  fn: "crypto.randomBytesCb",
3912
- args: [lowerer.lowerExprExpecting(args[0], F64), cryptoBytesCallback(lowerer, args[1])],
4567
+ args: [lowerer.lowerExprExpecting(args[0], F64), errorFirstBytesCallback(lowerer, args[1], "crypto")],
3913
4568
  type: VOID,
3914
4569
  loc,
3915
4570
  };
@@ -3927,7 +4582,7 @@ export function lowerCryptoModuleCall(lowerer, expr, bi, loc) {
3927
4582
  lowerer.lowerExprExpecting(args[2], F64),
3928
4583
  lowerer.lowerExprExpecting(args[3], F64),
3929
4584
  cryptoAlgorithm(lowerer, args[4], "crypto.pbkdf2"),
3930
- cryptoBytesCallback(lowerer, args[5]),
4585
+ errorFirstBytesCallback(lowerer, args[5], "crypto"),
3931
4586
  ],
3932
4587
  type: VOID,
3933
4588
  loc,
@@ -4055,7 +4710,7 @@ export function lowerUrlMethodCall(lowerer, call, access) {
4055
4710
  const receiver = lowerer.lowerExpr(access.expression);
4056
4711
  return { kind: "libCall", fn: "url.href", args: [receiver], type: STRING, loc: locOf(call) };
4057
4712
  }
4058
- lowerer.noLowering(`URL.${name}`, call, "protocol, pathname, href, and toString() are the supported URL members", lowerer.checker.getSymbolAtLocation(access.name));
4713
+ 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));
4059
4714
  }
4060
4715
  /** `new URLSearchParams(init?)` — the WHATWG constructor's lowered init
4061
4716
  * shapes: omitted / literal `undefined` (empty list), a string (parsed,
@@ -4637,7 +5292,7 @@ export function lowerStatsMethodCall(lowerer, call, access) {
4637
5292
  : "stats.isSymbolicLink";
4638
5293
  return { kind: "libCall", fn, args: [receiver], type: BOOL, loc: locOf(call) };
4639
5294
  }
4640
- lowerer.noLowering(`Stats.${name}`, call, "isFile(), isDirectory(), isSymbolicLink(), size, blocks, nlink, atimeMs, and mtimeMs are the supported Stats members", lowerer.checker.getSymbolAtLocation(access.name));
5295
+ 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));
4641
5296
  }
4642
5297
  /** Calls on native crypto.Hash/Hmac handles. The handle survives locals,
4643
5298
  * aliases, loops, and returns; update() retains and returns the receiver,
@@ -4810,6 +5465,15 @@ export function lowerBuiltinExtraProperty(lowerer, expr) {
4810
5465
  };
4811
5466
  return lowerer.maybeNarrow(read, expr);
4812
5467
  }
5468
+ if (kind === "child" && name === "connected") {
5469
+ return {
5470
+ kind: "libCall",
5471
+ fn: "child.connected",
5472
+ args: [lowerer.lowerExpr(expr.expression)],
5473
+ type: BOOL,
5474
+ loc,
5475
+ };
5476
+ }
4813
5477
  if (kind === "child")
4814
5478
  return null; // pid/exitCode/killed live in lowerIntrinsicProperty
4815
5479
  if (kind === "childWriter") {
@@ -4823,7 +5487,8 @@ export function lowerBuiltinExtraProperty(lowerer, expr) {
4823
5487
  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));
4824
5488
  }
4825
5489
  if (kind === "stats" &&
4826
- (name === "blocks" || name === "nlink" || name === "atimeMs" || name === "mtimeMs")) {
5490
+ (name === "dev" || name === "ino" || name === "blocks" || name === "nlink" ||
5491
+ name === "atimeMs" || name === "mtimeMs" || name === "ctimeMs")) {
4827
5492
  const receiver = lowerer.lowerExpr(expr.expression);
4828
5493
  const fn = `stats.${name}`;
4829
5494
  return { kind: "libCall", fn, args: [receiver], type: F64, loc };
@@ -4850,6 +5515,63 @@ export function lowerBuiltinExtraProperty(lowerer, expr) {
4850
5515
  * lowerIntrinsicProperty). Everything else @types/node declares on
4851
5516
  * ChildProcess (stdout, once, ...) fences member-qualified. Null for
4852
5517
  * non-child receivers. */
5518
+ function ignoredListenerReturn(type) {
5519
+ return type.kind === "void" || (type.kind === "promise" && type.inner.kind === "void");
5520
+ }
5521
+ function ipcJsonMessage(lowerer, node, loc) {
5522
+ const value = lowerer.lowerExpr(node);
5523
+ if (!lowerer.jsonStringifySafe(value.type) && value.type.kind !== "dyn") {
5524
+ 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)`);
5525
+ }
5526
+ return { kind: "jsonStringify", value, type: STRING, loc };
5527
+ }
5528
+ function ipcMessageListener(lowerer, node) {
5529
+ const cb = lowerer.lowerExpr(node);
5530
+ if (cb.type.kind !== "func" || cb.type.params.length > 1 || !ignoredListenerReturn(cb.type.ret)) {
5531
+ lowerer.unsupported("SC1090", node, "message listeners take zero or one parameter and may return void or Promise<void>");
5532
+ }
5533
+ const param = cb.type.params[0];
5534
+ if (param !== undefined && param.kind !== "dyn" && !lowerer.jsonSafe(param)) {
5535
+ lowerer.unsupported("SC1090", node, `message listeners whose parameter is not JSON-shaped or unknown (got '${lowerer.fmt(param)}')`);
5536
+ }
5537
+ return cb;
5538
+ }
5539
+ function ipcDisconnectListener(lowerer, node) {
5540
+ const cb = lowerer.lowerExpr(node);
5541
+ if (cb.type.kind !== "func" || cb.type.params.length !== 0 || cb.type.ret.kind !== "void") {
5542
+ lowerer.unsupported("SC1090", node, "disconnect listeners take no parameters and return void");
5543
+ }
5544
+ return cb;
5545
+ }
5546
+ function ipcSendCallback(lowerer, node) {
5547
+ const cb = lowerer.lowerExpr(node);
5548
+ if (cb.type.kind !== "func" || cb.type.params.length > 1 || cb.type.ret.kind !== "void") {
5549
+ lowerer.unsupported("SC1090", node, "send callbacks take () or (error: Error | null) and return void");
5550
+ }
5551
+ const param = cb.type.params[0];
5552
+ if (param !== undefined) {
5553
+ const def = param.kind === "union" ? lowerer.unions.get(param.unionId) : undefined;
5554
+ const valid = !!def && def.arms.length === 2 &&
5555
+ def.arms.some((arm) => arm.kind === "nullT") &&
5556
+ def.arms.some((arm) => arm.kind === "object" && arm.className === "%Error");
5557
+ if (!valid) {
5558
+ lowerer.unsupported("SC1090", node, `send callbacks whose parameter is not 'Error | null' (got '${lowerer.fmt(param)}')`);
5559
+ }
5560
+ }
5561
+ return cb;
5562
+ }
5563
+ function lowerProcessIpcSend(lowerer, call) {
5564
+ const loc = locOf(call);
5565
+ if (call.arguments.length < 1 || call.arguments.length > 2 || call.arguments.some(ts.isSpreadElement)) {
5566
+ lowerer.noLowering(`process.send with ${call.arguments.length} arguments`, call, "send(message[, callback]) is supported; handle transfer and options are not");
5567
+ }
5568
+ const json = ipcJsonMessage(lowerer, call.arguments[0], loc);
5569
+ if (call.arguments[1] === undefined) {
5570
+ return { kind: "libCall", fn: "process.send", args: [json], type: BOOL, loc };
5571
+ }
5572
+ const callback = ipcSendCallback(lowerer, call.arguments[1]);
5573
+ return { kind: "libCall", fn: "process.sendCb", args: [json, callback], type: BOOL, loc };
5574
+ }
4853
5575
  export function lowerChildMethodCall(lowerer, call, access) {
4854
5576
  if (call.questionDotToken || access.questionDotToken)
4855
5577
  return null;
@@ -4859,20 +5581,63 @@ export function lowerChildMethodCall(lowerer, call, access) {
4859
5581
  return null;
4860
5582
  const name = access.name.text;
4861
5583
  const loc = locOf(call);
4862
- if (name === "on" && call.arguments.length === 2) {
5584
+ if (name === "send") {
5585
+ if (call.arguments.length < 1 || call.arguments.length > 2 || call.arguments.some(ts.isSpreadElement)) {
5586
+ lowerer.noLowering(`child.send with ${call.arguments.length} arguments`, call, "send(message[, callback]) is supported; handle transfer and options are not");
5587
+ }
5588
+ const receiver = lowerer.lowerExpr(access.expression);
5589
+ const json = ipcJsonMessage(lowerer, call.arguments[0], loc);
5590
+ if (call.arguments[1] === undefined) {
5591
+ return { kind: "libCall", fn: "child.send", args: [receiver, json], type: BOOL, loc };
5592
+ }
5593
+ const callback = ipcSendCallback(lowerer, call.arguments[1]);
5594
+ return { kind: "libCall", fn: "child.sendCb", args: [receiver, json, callback], type: BOOL, loc };
5595
+ }
5596
+ if (name === "disconnect" && call.arguments.length === 0) {
5597
+ if (!ts.isExpressionStatement(call.parent)) {
5598
+ lowerer.unsupported("SC1090", call, "using the result of child.disconnect() (call it as its own statement)");
5599
+ }
5600
+ return {
5601
+ kind: "libCall",
5602
+ fn: "child.disconnect",
5603
+ args: [lowerer.lowerExpr(access.expression)],
5604
+ type: VOID,
5605
+ loc,
5606
+ };
5607
+ }
5608
+ if ((name === "on" || name === "once") && call.arguments.length === 2) {
4863
5609
  const evT = lowerer.typeOf(call.arguments[0]);
4864
5610
  const event = evT.isStringLiteralType() ? evT.value : null;
4865
- if (event !== "exit" && event !== "error") {
4866
- lowerer.noLowering(`child.on(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call.arguments[0], '"exit" and "error" are the supported child events (as literals)');
5611
+ if (event !== "exit" && event !== "close" && event !== "error" && event !== "message" && event !== "disconnect") {
5612
+ 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)');
4867
5613
  }
4868
5614
  if (!ts.isExpressionStatement(call.parent)) {
4869
- lowerer.unsupported("SC1090", call, "chaining child.on(...) (the result is void here — register each listener as its own statement)");
5615
+ lowerer.unsupported("SC1090", call, `chaining child.${name}(...) (the result is void here — register each listener as its own statement)`);
4870
5616
  }
4871
5617
  const receiver = lowerer.lowerExpr(access.expression);
5618
+ const once = { kind: "boolLit", value: name === "once", type: BOOL, loc };
5619
+ if (event === "message") {
5620
+ return {
5621
+ kind: "libCall",
5622
+ fn: "child.onMessage",
5623
+ args: [receiver, ipcMessageListener(lowerer, call.arguments[1]), once],
5624
+ type: VOID,
5625
+ loc,
5626
+ };
5627
+ }
5628
+ if (event === "disconnect") {
5629
+ return {
5630
+ kind: "libCall",
5631
+ fn: "child.onDisconnect",
5632
+ args: [receiver, ipcDisconnectListener(lowerer, call.arguments[1]), once],
5633
+ type: VOID,
5634
+ loc,
5635
+ };
5636
+ }
4872
5637
  const cb = lowerer.lowerExpr(call.arguments[1]);
4873
- if (cb.type.kind !== "func" || cb.type.params.length > (event === "exit" ? 2 : 1)) {
4874
- lowerer.unsupported("SC1090", call.arguments[1], event === "exit"
4875
- ? "exit listeners with more than two parameters (use (code, signal), (code), or ())"
5638
+ if (cb.type.kind !== "func" || cb.type.params.length > (event === "error" ? 1 : 2)) {
5639
+ lowerer.unsupported("SC1090", call.arguments[1], event !== "error"
5640
+ ? `${event} listeners with more than two parameters (use (code, signal), (code), or ())`
4876
5641
  : "error listeners with more than one parameter (use (err) or ())");
4877
5642
  }
4878
5643
  if (cb.type.ret.kind !== "void") {
@@ -4882,7 +5647,7 @@ export function lowerChildMethodCall(lowerer, call, access) {
4882
5647
  lowerer.unsupported("SC1090", call.arguments[1], "listeners returning a value (make the callback body a block, or return nothing)");
4883
5648
  }
4884
5649
  const param = cb.type.params[0];
4885
- if (event === "exit") {
5650
+ if (event === "exit" || event === "close") {
4886
5651
  const armsOk = param === undefined ||
4887
5652
  (param.kind === "union" &&
4888
5653
  (() => {
@@ -4892,7 +5657,7 @@ export function lowerChildMethodCall(lowerer, call, access) {
4892
5657
  def.arms[1].kind === "nullT");
4893
5658
  })());
4894
5659
  if (!armsOk) {
4895
- lowerer.unsupported("SC1090", call.arguments[1], `exit listeners whose parameter is not 'number | null' (got '${lowerer.fmt(param)}')`);
5660
+ lowerer.unsupported("SC1090", call.arguments[1], `${event} listeners whose parameter is not 'number | null' (got '${lowerer.fmt(param)}')`);
4896
5661
  }
4897
5662
  // The optional SECOND parameter is Node's signal: the terminating
4898
5663
  // signal's name as `Signals | null` — a string | null union here
@@ -4907,9 +5672,9 @@ export function lowerChildMethodCall(lowerer, call, access) {
4907
5672
  def.arms.some((a) => a.kind === "nullT"));
4908
5673
  })());
4909
5674
  if (!sigOk) {
4910
- lowerer.unsupported("SC1090", call.arguments[1], `exit listeners whose signal parameter is not 'Signals | null' (got '${lowerer.fmt(sigParam)}')`);
5675
+ lowerer.unsupported("SC1090", call.arguments[1], `${event} listeners whose signal parameter is not 'Signals | null' (got '${lowerer.fmt(sigParam)}')`);
4911
5676
  }
4912
- return { kind: "libCall", fn: "child.onExit", args: [receiver, cb], type: VOID, loc };
5677
+ return { kind: "libCall", fn: event === "close" ? "child.onClose" : "child.onExit", args: [receiver, cb], type: VOID, loc };
4913
5678
  }
4914
5679
  if (param !== undefined && !(param.kind === "object" && param.className === "%Error")) {
4915
5680
  lowerer.unsupported("SC1090", call.arguments[1], `error listeners whose parameter is not 'Error' (got '${lowerer.fmt(param)}')`);
@@ -4948,7 +5713,7 @@ export function lowerChildMethodCall(lowerer, call, access) {
4948
5713
  const receiver = lowerer.lowerExpr(access.expression);
4949
5714
  return { kind: "libCall", fn: "child.unref", args: [receiver], type: VOID, loc };
4950
5715
  }
4951
- 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));
5716
+ 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));
4952
5717
  }
4953
5718
  /** Method calls on piped child-output stream receivers (child.stdout /
4954
5719
  * child.stderr — the childStream kind): on/once("data" | "end").
@@ -4970,6 +5735,11 @@ export function lowerChildStreamMethodCall(lowerer, call, access) {
4970
5735
  return null;
4971
5736
  const name = access.name.text;
4972
5737
  const loc = locOf(call);
5738
+ if (name === "setEncoding" && call.arguments.length === 1) {
5739
+ const receiver = lowerer.lowerExprExpecting(access.expression, CHILDSTREAM_T);
5740
+ const enc = bufEncoding(lowerer, "child stream setEncoding", call.arguments[0]);
5741
+ return { kind: "libCall", fn: "stream.childSetEncoding", args: [receiver, strLit(enc, loc)], type: CHILDSTREAM_T, loc };
5742
+ }
4973
5743
  if ((name === "on" || name === "once") && call.arguments.length === 2) {
4974
5744
  const evT = lowerer.typeOf(call.arguments[0]);
4975
5745
  const event = evT.isStringLiteralType() ? evT.value : null;
@@ -4998,12 +5768,12 @@ export function lowerChildStreamMethodCall(lowerer, call, access) {
4998
5768
  const def = lowerer.unions.get(p.unionId);
4999
5769
  return !!def && def.arms.some((a) => a.kind === "bytes" && a.elem === "u8");
5000
5770
  };
5001
- if (param !== undefined && !(param.kind === "bytes" && param.elem === "u8") && !unionOk(param)) {
5002
- lowerer.unsupported("SC1090", call.arguments[1], `data listeners whose parameter is not 'Buffer' (or a Buffer-armed union; got '${lowerer.fmt(param)}')`);
5771
+ if (param !== undefined && param.kind !== "string" && !(param.kind === "bytes" && param.elem === "u8") && !unionOk(param)) {
5772
+ lowerer.unsupported("SC1090", call.arguments[1], `data listeners whose parameter is not 'Buffer', 'string', or a Buffer-armed union (got '${lowerer.fmt(param)}')`);
5003
5773
  }
5004
- return { kind: "libCall", fn: "stream.onData", args: [receiver, cb, once], type: VOID, loc };
5774
+ return { kind: "libCall", fn: param?.kind === "string" ? "stream.onDataStr" : "stream.onData", args: [receiver, cb, once], type: VOID, loc };
5005
5775
  }
5006
- lowerer.noLowering(`ReadableStream.${name}`, call, 'on/once("data" | "end", cb) are the supported child-stream members', lowerer.checker.getSymbolAtLocation(access.name));
5776
+ lowerer.noLowering(`ReadableStream.${name}`, call, 'setEncoding(encoding) and on/once("data" | "end", cb) are the supported child-stream members', lowerer.checker.getSymbolAtLocation(access.name));
5007
5777
  }
5008
5778
  /** Method calls on a piped child stdin writer. Writes copy one string or
5009
5779
  * Uint8Array into the nonblocking runtime queue and return the backpressure
@@ -5290,6 +6060,19 @@ export function lowerProcessStreamProperty(lowerer, expr) {
5290
6060
  let recv = expr.expression;
5291
6061
  while (ts.isParenthesizedExpression(recv) || ts.isAsExpression(recv) || ts.isTypeAssertion(recv))
5292
6062
  recv = recv.expression;
6063
+ // Browser-compatible packages commonly write
6064
+ // `(process.stdout || {}).isTTY`. Node's stream object is always
6065
+ // present and truthy, so the fallback cannot run; match the left
6066
+ // receiver exactly as the value-level always-truthy `||` lowering
6067
+ // does, preserving the native isatty/columns answer.
6068
+ if (ts.isBinaryExpression(recv) &&
6069
+ recv.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
6070
+ let left = recv.left;
6071
+ while (ts.isParenthesizedExpression(left) || ts.isAsExpression(left) || ts.isTypeAssertion(left))
6072
+ left = left.expression;
6073
+ if (ts.isPropertyAccessExpression(left))
6074
+ recv = left;
6075
+ }
5293
6076
  if (!ts.isPropertyAccessExpression(recv))
5294
6077
  return null;
5295
6078
  const stream = lowerer.stdlibGlobalMember(recv, "process");
@@ -5399,6 +6182,9 @@ export function lowerProcessProperty(lowerer, expr) {
5399
6182
  if (member === "argv") {
5400
6183
  return { kind: "libCall", fn: "process.argv", args: [], type: arrayOf(STRING), loc };
5401
6184
  }
6185
+ if (member === "connected") {
6186
+ return { kind: "libCall", fn: "process.connected", args: [], type: BOOL, loc };
6187
+ }
5402
6188
  // process.execArgv: the extra CLI arguments Node itself consumed — a
5403
6189
  // compiled binary consumed none, so the honest answer is a fresh [].
5404
6190
  if (member === "execArgv") {
@@ -5477,48 +6263,55 @@ export function envValueType(lowerer) {
5477
6263
  * @types/node, a user alias reshaping the result) fences honestly. Key
5478
6264
  * and row order follow getifaddrs enumeration — Node itself guarantees
5479
6265
  * no order (compare structurally). */
5480
- /** `fs.readdirSync(path, { withFileTypes: true })` — Dirent rows: name +
5481
- * parentPath (the path argument as given, Node's own rule) + the hidden
5482
- * %dtype entry kind (libuv's UV_DIRENT encoding; DT_UNKNOWN falls back
5483
- * to lstat, Node's getDirents rule). The options literal is checked
5484
- * member-by-member; the result type must be the interned Dirent record
5485
- * array from type-mapper.ts — anything else (encoding: 'buffer', a user alias
5486
- * reshaping Dirent) fences honestly. */
5487
- function lowerFsReaddirTypesCall(lowerer, call, loc) {
6266
+ /** `fs.readdirSync` / `fs.promises.readdir` with `{ withFileTypes: true }`
6267
+ * — Dirent rows: name + parentPath (the path argument as given, Node's
6268
+ * own rule) + the hidden %dtype entry kind (libuv's UV_DIRENT encoding;
6269
+ * DT_UNKNOWN falls back to lstat, Node's getDirents rule). The options
6270
+ * literal is checked member-by-member; the result type must carry the
6271
+ * interned Dirent record array from type-mapper.ts — anything else
6272
+ * (encoding: 'buffer', a user alias reshaping Dirent) fences honestly. */
6273
+ function lowerFsReaddirTypesCall(lowerer, call, loc, promiseForm) {
6274
+ const operation = promiseForm ? "fs.promises.readdir" : "readdirSync";
6275
+ const spelling = promiseForm ? "readdir" : "readdirSync";
5488
6276
  const optsNode = call.arguments[1];
5489
6277
  if (!ts.isObjectLiteralExpression(optsNode)) {
5490
- lowerer.noLowering("readdirSync with a non-literal options argument", optsNode, "pass the options inline so each member can be checked: readdirSync(path, { withFileTypes: true })");
6278
+ lowerer.noLowering(`${operation} with a non-literal options argument`, optsNode, `pass the options inline so each member can be checked: ${spelling}(path, { withFileTypes: true })`);
5491
6279
  }
5492
6280
  let sawWithFileTypes = false;
5493
6281
  for (const p of optsNode.properties) {
5494
6282
  const m = optionMember(p);
5495
6283
  if (!m) {
5496
- lowerer.noLowering("readdirSync with this options shape", p, "spreads and computed keys have no lowering — write each member inline");
6284
+ lowerer.noLowering(`${operation} with this options shape`, p, "spreads and computed keys have no lowering — write each member inline");
5497
6285
  }
5498
6286
  if (m.name === "withFileTypes") {
5499
6287
  const t = lowerer.typeOf(m.value);
5500
6288
  if (!(t.flags & ts.TypeFlags.BooleanLiteral) || lowerer.checker.typeToString(t) !== "true") {
5501
- lowerer.noLowering("readdirSync with a non-literal-true withFileTypes", m.value, "withFileTypes: true is the Dirent form; omit the options for plain names");
6289
+ lowerer.noLowering(`${operation} with a non-literal-true withFileTypes`, m.value, "withFileTypes: true is the Dirent form; omit the options for plain names");
5502
6290
  }
5503
6291
  sawWithFileTypes = true;
5504
6292
  }
5505
6293
  else if (m.name === "encoding") {
5506
6294
  const t = lowerer.typeOf(m.value);
5507
6295
  if (!t.isStringLiteralType() || (t.value !== "utf8" && t.value !== "utf-8")) {
5508
- lowerer.noLowering("readdirSync with a non-utf8 encoding", m.value, "names decode as utf8 (the default); encoding: 'buffer' has no lowering");
6296
+ lowerer.noLowering(`${operation} with a non-utf8 encoding`, m.value, "names decode as utf8 (the default); encoding: 'buffer' has no lowering");
5509
6297
  }
5510
6298
  }
5511
6299
  else {
5512
6300
  // The options-record stance: recursive (a documented knob with
5513
6301
  // no lowering) fences by name; undocumented keys drop like Node.
5514
- 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");
6302
+ 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");
5515
6303
  }
5516
6304
  }
5517
6305
  if (!sawWithFileTypes) {
5518
- lowerer.noLowering("readdirSync with 2 arguments", call, "readdirSync(path) lists names; readdirSync(path, { withFileTypes: true }) lists Dirents");
6306
+ lowerer.noLowering(`${operation} with 2 arguments`, call, `${spelling}(path) lists names; ${spelling}(path, { withFileTypes: true }) lists Dirents`);
5519
6307
  }
5520
- 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");
5521
- const result = lowerer.mapTypeOf(lowerer.typeOf(call));
6308
+ 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");
6309
+ const callType = lowerer.mapTypeOf(lowerer.typeOf(call));
6310
+ if (!callType)
6311
+ fence();
6312
+ const result = promiseForm
6313
+ ? callType.kind === "promise" ? callType.inner : fence()
6314
+ : callType;
5522
6315
  if (result?.kind !== "array" || result.elem.kind !== "record")
5523
6316
  fence();
5524
6317
  const shape = lowerer.shapes.get(result.elem.shapeId);
@@ -5530,7 +6323,13 @@ function lowerFsReaddirTypesCall(lowerer, call, loc) {
5530
6323
  fence();
5531
6324
  }
5532
6325
  const path = lowerer.lowerExprExpecting(call.arguments[0], STRING);
5533
- return { kind: "libCall", fn: "fs.readdirTypesSync", args: [path], type: result, loc };
6326
+ return {
6327
+ kind: "libCall",
6328
+ fn: promiseForm ? "fsp.readdirTypes" : "fs.readdirTypesSync",
6329
+ args: [path],
6330
+ type: callType,
6331
+ loc,
6332
+ };
5534
6333
  }
5535
6334
  /** `d.isFile()` / `d.isDirectory()` / `d.isSymbolicLink()` on a Dirent-
5536
6335
  * typed receiver (the interned record — provenance via the checker's
@@ -5864,13 +6663,23 @@ export function lowerProcessEnvGet(lowerer, expr) {
5864
6663
  }
5865
6664
  /** `process.exit(code)` / `process.cwd()` → libCall. The fallback
5866
6665
  * declaration makes exit's code required; @types/node declares it
5867
- * optional, and a bare `process.exit()` lowers as exit(0) — exactly
5868
- * Node's behavior when process.exitCode was never set (setting exitCode
5869
- * is fenced like every other unsupported process member, so "never set"
5870
- * always holds in a compiled program). */
6666
+ * optional. A bare `process.exit()` uses the current exitCode, or zero
6667
+ * when unset. */
5871
6668
  export function lowerProcessMethodCall(lowerer, call, access) {
5872
6669
  if (call.questionDotToken)
5873
6670
  return null;
6671
+ const directProcessMember = lowerer.stdlibGlobalMember(access, "process");
6672
+ if (directProcessMember === "send")
6673
+ return lowerProcessIpcSend(lowerer, call);
6674
+ if (directProcessMember === "disconnect") {
6675
+ if (call.arguments.length !== 0) {
6676
+ lowerer.noLowering(`process.disconnect with ${call.arguments.length} arguments`, call);
6677
+ }
6678
+ if (!ts.isExpressionStatement(call.parent)) {
6679
+ lowerer.unsupported("SC1090", call, "using the result of process.disconnect() (call it as its own statement)");
6680
+ }
6681
+ return { kind: "libCall", fn: "process.disconnect", args: [], type: VOID, loc: locOf(call) };
6682
+ }
5874
6683
  // process.stdout.write(s[, encoding][, callback]) and stderr's twin:
5875
6684
  // raw bytes, no newline or formatting. stdout shares console.log's
5876
6685
  // promptly-submitted stream, preserving source order. Node's boolean
@@ -6131,6 +6940,21 @@ export function lowerProcessMethodCall(lowerer, call, access) {
6131
6940
  }
6132
6941
  const evT = lowerer.typeOf(call.arguments[0]);
6133
6942
  const event = evT.isStringLiteralType() ? evT.value : null;
6943
+ if ((event === "message" || event === "disconnect") && !isOff) {
6944
+ if (!ts.isExpressionStatement(call.parent)) {
6945
+ lowerer.unsupported("SC1090", call, "chaining process IPC listener registration (register each listener as its own statement)");
6946
+ }
6947
+ const callback = event === "message"
6948
+ ? ipcMessageListener(lowerer, call.arguments[1])
6949
+ : ipcDisconnectListener(lowerer, call.arguments[1]);
6950
+ return {
6951
+ kind: "libCall",
6952
+ fn: event === "message" ? "process.onMessage" : "process.onDisconnect",
6953
+ args: [callback, boolLit(member === "once", loc)],
6954
+ type: VOID,
6955
+ loc,
6956
+ };
6957
+ }
6134
6958
  // own(), not a bare index: the key is a USER-written event name,
6135
6959
  // and `{ SIGINT: 2 }["__proto__"]` answers Object.prototype — an
6136
6960
  // object flowed into a numLit and emitted itself into the C
@@ -6173,7 +6997,7 @@ export function lowerProcessMethodCall(lowerer, call, access) {
6173
6997
  };
6174
6998
  }
6175
6999
  if (signo === undefined && event !== "exit") {
6176
- 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)');
7000
+ 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)');
6177
7001
  }
6178
7002
  if (!ts.isExpressionStatement(call.parent)) {
6179
7003
  lowerer.unsupported("SC1090", call, "chaining process listener registration (the result is void here — register each listener as its own statement)");
@@ -6481,7 +7305,7 @@ export function lowerProcessMethodCall(lowerer, call, access) {
6481
7305
  const arg = call.arguments[0];
6482
7306
  const code = arg !== undefined
6483
7307
  ? lowerer.lowerExprExpecting(arg, F64)
6484
- : { kind: "numLit", value: 0, type: F64, loc };
7308
+ : { kind: "libCall", fn: "process.currentExitCode", args: [], type: F64, loc };
6485
7309
  return { kind: "libCall", fn: "process.exit", args: [code], type: VOID, loc };
6486
7310
  }
6487
7311
  return null; // process.argv(...) etc. are tsc errors before lowering
@@ -6593,6 +7417,16 @@ export function lowerDateCall(lowerer, call, access) {
6593
7417
  }
6594
7418
  return { kind: "libCall", fn: "date.now", args: [], type: F64, loc };
6595
7419
  }
7420
+ // Date.parse uses the same bounded date-string parser as
7421
+ // new Date(dateString).getTime(); keep their NaN and TimeClip behavior
7422
+ // identical for the ISO timestamps used by CLI applications.
7423
+ if (lowerer.stdlibGlobalMember(access, "Date") === "parse") {
7424
+ if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0])) {
7425
+ lowerer.noLowering(`Date.parse with ${call.arguments.length} arguments`, call);
7426
+ }
7427
+ const input = lowerer.lowerExprExpecting(call.arguments[0], STRING);
7428
+ return { kind: "libCall", fn: "date.parse", args: [input], type: F64, loc };
7429
+ }
6596
7430
  // Date.UTC(year[, month[, date[, hours[, minutes[, seconds[, ms]]]]]]):
6597
7431
  // a pure function of its numbers — the runtime's MakeDay/MakeTime/
6598
7432
  // TimeClip. Omitted trailing arguments complete with the spec's
@@ -6636,7 +7470,7 @@ export function lowerDateCall(lowerer, call, access) {
6636
7470
  if (name === "toISOString") {
6637
7471
  return { kind: "libCall", fn: "date.toISOStringValue", args: [receiver], type: STRING, loc };
6638
7472
  }
6639
- const fn = DATE_GETTER_FNS[name];
7473
+ const fn = own(DATE_GETTER_FNS, name);
6640
7474
  if (fn !== undefined) {
6641
7475
  return { kind: "libCall", fn, args: [receiver], type: F64, loc };
6642
7476
  }
@@ -6997,11 +7831,10 @@ export function lowerStringStaticCall(lowerer, call, access) {
6997
7831
  const packed = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
6998
7832
  return { kind: "libCall", fn: "string.fromCharCode", args: [packed], type: STRING, loc };
6999
7833
  }
7000
- /** `s.lastIndexOf(needle)` on string receivers — a libCall rather than a
7001
- * strIntrinsic, but the same UTF-16 index semantics as
7002
- * indexOf. The lib's fromIndex parameter has no lowering (Node clamps
7003
- * it with ToIntegerOrInfinity; nothing in the corpus wants it) and
7004
- * fences per site. Null for non-string receivers / other members. */
7834
+ /** `s.lastIndexOf(searchValue?, position?)` on string receivers, using UTF-16
7835
+ * indices. An omitted search value searches for "undefined". Omitted or undefined positions clamp to the string's end;
7836
+ * MAX_SAFE_INTEGER has the same effect for every representable string.
7837
+ * Null for non-string receivers and other members. */
7005
7838
  export function lowerStringLastIndexOfCall(lowerer, call, access) {
7006
7839
  if (call.questionDotToken || access.questionDotToken)
7007
7840
  return null;
@@ -7012,12 +7845,18 @@ export function lowerStringLastIndexOfCall(lowerer, call, access) {
7012
7845
  if (!lowerer.isStdlibMember(access))
7013
7846
  return null;
7014
7847
  const loc = locOf(call);
7015
- if (call.arguments.length !== 1) {
7016
- lowerer.noLowering("lastIndexOf with a fromIndex argument", call, "the one-argument form lowers");
7848
+ if (call.arguments.length > 2 || call.arguments.some(ts.isSpreadElement)) {
7849
+ lowerer.noLowering("lastIndexOf with this argument shape", call, "pass no arguments, or a search value with an optional numeric position");
7017
7850
  }
7018
7851
  const receiver = lowerer.lowerExprExpecting(access.expression, STRING);
7852
+ const positionNode = call.arguments[1];
7853
+ if (!positionNode) {
7854
+ const needle = lowerStringSearchArgument(lowerer, call.arguments[0], loc);
7855
+ return { kind: "libCall", fn: "string.lastIndexOf", args: [receiver, needle], type: F64, loc };
7856
+ }
7019
7857
  const needle = lowerer.lowerExprExpecting(call.arguments[0], STRING);
7020
- return { kind: "libCall", fn: "string.lastIndexOf", args: [receiver, needle], type: F64, loc };
7858
+ const position = lowerOptionalArgument(lowerer, positionNode, F64, numLit(Number.MAX_SAFE_INTEGER, loc));
7859
+ return { kind: "libCall", fn: "string.lastIndexOfFrom", args: [receiver, needle, position], type: F64, loc };
7021
7860
  }
7022
7861
  /** `Promise.race([...])` on THE Promise global: the entries lower
7023
7862
  * individually (the array never materializes — promise-element arrays
@@ -7432,6 +8271,8 @@ export function lowerProcessOptionalMethodCall(lowerer, expr) {
7432
8271
  if (!ts.isPropertyAccessExpression(expr.expression))
7433
8272
  return null;
7434
8273
  const member = lowerer.stdlibGlobalMember(expr.expression, "process");
8274
+ if (member === "send")
8275
+ return lowerProcessIpcSend(lowerer, expr);
7435
8276
  if (member !== "getuid" && member !== "getgid")
7436
8277
  return null;
7437
8278
  if (expr.arguments.length !== 0) {