@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
@@ -11,11 +11,11 @@ import { pathToFileURL } from "node:url";
11
11
  import { wasiGuestPath } from "../../wasi-paths.js";
12
12
  import { BIGINT_T, BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, JSVAL, NULL_T, REF_TRUTHY_KINDS, REGEX, RUNTIME_ERROR_CLASSES, SEARCH_PARAMS_T, STRING, UNDEFINED_T, VOID, arrayOf, canAdaptDynFuncTo, canBoxFuncIntoDyn, funcOf, isDynTypedRefType, isJsonSafeType, isSupportedArrayElem, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey, unionFuncSetArmsOk } from "../../ir/ir.js";
13
13
  import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeEsmFile, locOf } from "../program.js";
14
- import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModulesArrayLit, builtinModuleFnOf, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
14
+ import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModulesArrayLit, builtinModuleFnOf, COMPOUND_ASSIGN_OPS, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
15
15
  import { blockedBindingUseDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
16
16
  import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
17
- import { lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall } from "./lower-containers.js";
18
- import { arrayValueStore } from "./array-values.js";
17
+ import { lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall, tryLowerNumericIndexRead } from "./lower-containers.js";
18
+ import { arrayValueRead, arrayValueStore } from "./array-values.js";
19
19
  import { npmStaticPackageOfPath } from "../npm-static.js";
20
20
  import { unsupportedModuleFeatureOf } from "../builtin-modules.js";
21
21
  import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js";
@@ -23,7 +23,7 @@ import { ambientNsRootOf, ambientUndefReadType, ambientUndefVarRootOf, ambientUn
23
23
  import { expandoMemberRead, expandoWritableTarget } from "./lower-expando.js";
24
24
  import { lowerSocketInstanceOf, lowerTlsRootCertificates } from "./lower-server.js";
25
25
  import { findGenericMethodOn, lowerStaticFieldRead } from "./lower-classes.js";
26
- import { bindingNeverReassigned, implicitMonoFile, lowerTaggedTemplate, nullishGenericBindingUnitOf, objLitGenericFnInfoOf, objLitGenericFnNodeOf, requireObjLitGenericReceiver } from "./lower-calls.js";
26
+ import { bindingNeverReassigned, funcTypeFromParamShapes, implicitMonoFile, lowerTaggedTemplate, nullishGenericBindingUnitOf, objLitGenericFnInfoOf, objLitGenericFnNodeOf, requireObjLitGenericReceiver } from "./lower-calls.js";
27
27
  import { mixinFnOfCallee } from "./lower-mixins.js";
28
28
  import { isConstAssertionTypeNode, isGenericCallableMemberType, isParseArgsDynTypeName, underConstAssertion, unitOnlyUnion } from "../type-mapper.js";
29
29
  import { lowerYield } from "./lower-generators.js";
@@ -34,6 +34,8 @@ import { hasOptionalChainGuard, isOptionalChainTail, isRequireMainFilename } fro
34
34
  import { conditionalSpreadOf, foldedStringKeyOf } from "./expressions/object-literals.js";
35
35
  import { tryLowerExpression } from "./expressions/try-lower-expression.js";
36
36
  import { fenceNodeModuleMutation, isNodeModuleValue, lowerNodeModuleIdentifier, lowerNodeModuleProperty, lowerRequireCacheElement, lowerRequireCacheHas, lowerRequireMainProperty } from "./lower-node-module.js";
37
+ import { lowerAbstractEquality } from "./abstract-equality.js";
38
+ import { coerceStringSearchValue, defaultAfterUndefined, lowerStaticallyUndefinedArgument } from "./optional-arguments.js";
37
39
  /** A template piece's RAW text (String.raw's contract: escapes stay
38
40
  * characters). 7's client AST ships no rawText at runtime (the typing
39
41
  * declares it; the serialized node data omits it), so the raw span comes
@@ -598,6 +600,7 @@ function lowerExprInner(lowerer, expr) {
598
600
  symbol: "symbol",
599
601
  map: "object", set: "object", promise: "object", bytes: "object",
600
602
  regexp: "object", generator: "object", classval: "function",
603
+ moduleNs: "object",
601
604
  undefinedT: "undefined", nullT: "object",
602
605
  };
603
606
  const folded = FOLD[operand.type.kind];
@@ -649,13 +652,22 @@ function lowerExprInner(lowerer, expr) {
649
652
  if (expr.text === "arguments" && lowerer.ctx.argumentsLocal) {
650
653
  return { kind: "varRef", localId: lowerer.ctx.argumentsLocal.id, type: DYN, loc };
651
654
  }
652
- // A `const x = promisify(execFile)` binding as a VALUE: the
653
- // promisified function never exists at runtime (its calls lower to
654
- // the interned async-exec helper) — call it directly.
655
+ // A compiler-projected adapter as a VALUE: the adapter has no
656
+ // runtime function object (its calls rewrite directly) — call it
657
+ // through its immutable binding instead.
655
658
  {
656
659
  const sym = lowerer.resolveValueSymbol(expr);
657
- if (sym && lowerer.promisifiedExecFile.has(sym)) {
658
- lowerer.unsupported("SC1090", expr, `a promisified execFile as a value (call '${expr.text}' directly)`);
660
+ const projection = sym ? lowerer.staticCallables.get(sym) : undefined;
661
+ if (projection?.kind === "promisified-exec-file" || projection?.kind === "promisified-builtin") {
662
+ lowerer.unsupported("SC1090", expr, `a promisified builtin as a value (call '${expr.text}' directly)`);
663
+ }
664
+ if (projection?.kind === "builtin-function") {
665
+ if (!isJsSourceFile(expr.getSourceFile())) {
666
+ const callable = lowerer.lowerBuiltinCallableValue(projection, loc);
667
+ if (callable)
668
+ return callable;
669
+ }
670
+ lowerer.unsupported("SC1090", expr, `a builtin function alias as an escaping value (call '${expr.text}' directly)`);
659
671
  }
660
672
  }
661
673
  // Union-typed bindings read through tsc's control-flow narrowing:
@@ -809,17 +821,12 @@ function lowerExprInner(lowerer, expr) {
809
821
  if (sig && lowerer.isTopLevelFnSymbol(expr)) {
810
822
  // A declared function used as a value: a zero-capture closure. The
811
823
  // backend interns one per function so `f === f` holds (JS identity).
812
- // The value's type is the completed ABI signature — exact-arity, so
813
- // optional/default/rest declarations pass the value fence first.
824
+ // The value's type is the completed ABI signature; optional/default
825
+ // slots and typed rest arrays pass the value fence first.
814
826
  lowerer.noteEdge(sig.name);
815
827
  // dynRest slots stay out of the VALUE type's param list (fn.length
816
828
  // semantics); the rest marker carries the trailing dyn-array ABI.
817
- const funcType = {
818
- kind: "func",
819
- params: sig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type),
820
- ret: sig.returnType,
821
- ...(sig.params.some((p) => p.mode === "dynRest") ? { rest: true } : {}),
822
- };
829
+ const funcType = funcTypeFromParamShapes(sig.params, sig.returnType);
823
830
  lowerer.requireExactArityValue(expr, expr, sig.params, funcType);
824
831
  return { kind: "closure", fnName: sig.name, captures: [], type: funcType, loc };
825
832
  }
@@ -865,6 +872,12 @@ function lowerExprInner(lowerer, expr) {
865
872
  if (bi.module === "module" && bi.member === "builtinModules") {
866
873
  return builtinModulesArrayLit(loc);
867
874
  }
875
+ if (bi.module === "http" && bi.member === "METHODS") {
876
+ return { kind: "libCall", fn: "http.methods", args: [], type: arrayOf(STRING), loc };
877
+ }
878
+ if (bi.module === "http" && bi.member === "STATUS_CODES") {
879
+ return { kind: "libCall", fn: "http.statusCodes", args: [], type: DYN, loc };
880
+ }
868
881
  // tls.rootCertificates: a runtime-valued module constant (the
869
882
  // cached bundled-CA array) — the one member read that lowers
870
883
  // to a libCall instead of a baked literal.
@@ -879,6 +892,11 @@ function lowerExprInner(lowerer, expr) {
879
892
  if (isJsSourceFile(expr.getSourceFile())) {
880
893
  return { kind: "strLit", value: `[builtin ${bi.module}.${bi.member}]`, type: STRING, loc };
881
894
  }
895
+ {
896
+ const callable = lowerer.lowerBuiltinCallableValue(bi, loc);
897
+ if (callable)
898
+ return callable;
899
+ }
882
900
  if (builtinModuleFnOf(lowerer, bi.module, bi.member)) {
883
901
  lowerer.unsupported("SC1090", expr, `library functions as values (call '${expr.text}' directly)`);
884
902
  }
@@ -889,7 +907,12 @@ function lowerExprInner(lowerer, expr) {
889
907
  // the namespace object has no representation — members lower at
890
908
  // their access sites only. The CommonJS namespace binding
891
909
  // (`const lib = require("./lib.js")`) gets the same fence.
892
- if (lowerer.builtinNamespaceModuleOf(expr) !== null || lowerer.cjsLocalModuleBindingOf(expr)) {
910
+ const builtinNamespace = lowerer.builtinNamespaceModuleOf(expr);
911
+ if (builtinNamespace !== null) {
912
+ const type = { kind: "moduleNs", moduleId: `builtin:${builtinNamespace}` };
913
+ return { kind: "moduleNsRef", moduleId: type.moduleId, type, loc };
914
+ }
915
+ if (lowerer.cjsLocalModuleBindingOf(expr)) {
893
916
  lowerer.unsupported("SC1090", expr, `module namespace objects as values (access '${expr.text}' members directly)`);
894
917
  }
895
918
  if (lowerer.islandGlobalFnOf(expr)) {
@@ -941,11 +964,14 @@ function lowerExprInner(lowerer, expr) {
941
964
  // type-mapper.ts pins the one concrete signature `(value: string) =>
942
965
  // primitive`; direct calls `String(x)` never reach here — the call
943
966
  // lowering intercepts them with the wider static coercions).
944
- // JavaScript sources keep the identity-token path below.
967
+ const jsPrimitiveCtorSelector = isJsSourceFile(expr.getSourceFile()) &&
968
+ expr.parent !== undefined &&
969
+ ts.isArrowFunction(expr.parent) &&
970
+ expr.parent.body === expr;
945
971
  if ((expr.text === "String" || expr.text === "Number" || expr.text === "Boolean") &&
946
- !isJsSourceFile(expr.getSourceFile()) &&
972
+ (!isJsSourceFile(expr.getSourceFile()) || jsPrimitiveCtorSelector) &&
947
973
  lowerer.isStdlibSymbol(lowerer.checker.getSymbolAtLocation(expr))) {
948
- return primitiveCtorClosure(lowerer, expr.text, loc);
974
+ return primitiveCtorClosure(lowerer, expr.text, loc, jsPrimitiveCtorSelector);
949
975
  }
950
976
  // The lib fence's IDENTIFIER chokepoint: the real standard library
951
977
  // resolves names the old minimal ambient world never declared
@@ -1081,6 +1107,15 @@ function lowerExprInner(lowerer, expr) {
1081
1107
  // representation (ambient namespaces compile to Node's
1082
1108
  // ReferenceError instead — the object never exists at runtime).
1083
1109
  {
1110
+ const moduleNs = lowerer.mapTypeOf(lowerer.typeOf(expr));
1111
+ if (moduleNs?.kind === "moduleNs") {
1112
+ return { kind: "moduleNsRef", moduleId: moduleNs.moduleId, type: moduleNs, loc };
1113
+ }
1114
+ const builtinNs = lowerer.builtinNamespaceModuleOf(expr);
1115
+ if (builtinNs !== null) {
1116
+ const type = { kind: "moduleNs", moduleId: `builtin:${builtinNs}` };
1117
+ return { kind: "moduleNsRef", moduleId: type.moduleId, type, loc };
1118
+ }
1084
1119
  const ns = lowerNsIdentifierValue(lowerer, expr);
1085
1120
  if (ns)
1086
1121
  return ns;
@@ -3084,10 +3119,10 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3084
3119
  const receiver = lowerer.lowerExpr(expr.expression);
3085
3120
  return { kind: "libCall", fn: "child.killed", args: [receiver], type: BOOL, loc };
3086
3121
  }
3087
- if (name === "on" || name === "kill" || name === "unref") {
3122
+ if (name === "on" || name === "once" || name === "kill" || name === "unref") {
3088
3123
  lowerer.unsupported("SC1090", expr, `child methods as values (call '${name}' directly)`);
3089
3124
  }
3090
- lowerer.noLowering(`ChildProcess.${name}`, expr, "on(\"exit\" | \"error\", cb), pid, exitCode, killed, kill(signal?), and unref() are the supported ChildProcess members", lowerer.checker.getSymbolAtLocation(expr.name));
3125
+ lowerer.noLowering(`ChildProcess.${name}`, expr, "on/once(\"exit\" | \"close\" | \"error\", cb), pid, exitCode, killed, kill(signal?), and unref() are the supported ChildProcess members", lowerer.checker.getSymbolAtLocation(expr.name));
3091
3126
  }
3092
3127
  if (kind === "spawnRes") {
3093
3128
  // The spawnSync-result reads. status is the interned `number | null`
@@ -3137,7 +3172,7 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3137
3172
  if (name === "isFile" || name === "isDirectory") {
3138
3173
  lowerer.unsupported("SC1090", expr, `Stats methods as values (call '${name}' directly)`);
3139
3174
  }
3140
- lowerer.noLowering(`Stats.${name}`, expr, "isFile(), isDirectory(), isSymbolicLink(), size, blocks, nlink, atimeMs, and mtimeMs are the supported Stats members", lowerer.checker.getSymbolAtLocation(expr.name));
3175
+ lowerer.noLowering(`Stats.${name}`, expr, "isFile(), isDirectory(), isSymbolicLink(), dev, ino, size, blocks, nlink, atimeMs, mtimeMs, and ctimeMs are the supported Stats members", lowerer.checker.getSymbolAtLocation(expr.name));
3141
3176
  }
3142
3177
  if (kind === "date") {
3143
3178
  const methods = new Set([
@@ -3178,22 +3213,30 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3178
3213
  // serialization: lowercased hostname, `:port` appended exactly when
3179
3214
  // a non-default port is present (scr_url_host — Node-exact,
3180
3215
  // opaque-path URLs answer ""); `hostname` is the stored port-less
3181
- // host field verbatim (Node would keep IPv6 brackets here, but the
3182
- // parser rejects IPv6 hosts — documented divergence — so the getter
3183
- // never sees one).
3184
- if (name === "protocol" || name === "pathname" || name === "href" || name === "host" || name === "hostname" || name === "search") {
3216
+ // host field verbatim, including IPv6 brackets.
3217
+ if (name === "protocol" || name === "origin" || name === "username" || name === "password" || name === "pathname" || name === "href" || name === "host" || name === "hostname" || name === "port" || name === "search" || name === "hash") {
3185
3218
  const receiver = lowerer.lowerExpr(expr.expression);
3186
3219
  const fn = name === "protocol"
3187
3220
  ? "url.protocol"
3188
- : name === "pathname"
3189
- ? "url.pathname"
3190
- : name === "host"
3191
- ? "url.host"
3192
- : name === "hostname"
3193
- ? "url.hostname"
3194
- : name === "search"
3195
- ? "url.search"
3196
- : "url.href";
3221
+ : name === "origin"
3222
+ ? "url.origin"
3223
+ : name === "username"
3224
+ ? "url.username"
3225
+ : name === "password"
3226
+ ? "url.password"
3227
+ : name === "pathname"
3228
+ ? "url.pathname"
3229
+ : name === "host"
3230
+ ? "url.host"
3231
+ : name === "hostname"
3232
+ ? "url.hostname"
3233
+ : name === "port"
3234
+ ? "url.port"
3235
+ : name === "search"
3236
+ ? "url.search"
3237
+ : name === "hash"
3238
+ ? "url.hash"
3239
+ : "url.href";
3197
3240
  return { kind: "libCall", fn, args: [receiver], type: STRING, loc: locOf(expr) };
3198
3241
  }
3199
3242
  // `u.searchParams`: the LIVE cached view (one identity per URL —
@@ -3206,7 +3249,7 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3206
3249
  if (name === "toString" || name === "toJSON") {
3207
3250
  lowerer.unsupported("SC1090", expr, `URL methods as values (call '${name}' directly)`);
3208
3251
  }
3209
- lowerer.noLowering(`URL.${name}`, expr, "protocol, pathname, href, host, hostname, search, searchParams, and toString() are the supported URL members", lowerer.checker.getSymbolAtLocation(expr.name));
3252
+ lowerer.noLowering(`URL.${name}`, expr, "protocol, origin, username, password, pathname, href, host, hostname, port, search, hash, searchParams, and toString() are the supported URL members", lowerer.checker.getSymbolAtLocation(expr.name));
3210
3253
  }
3211
3254
  if (kind === "searchParams") {
3212
3255
  const SEARCH_PARAMS_METHODS = new Set(["get", "getAll", "set", "append", "delete", "has", "sort", "toString", "forEach", "keys", "values", "entries"]);
@@ -3768,6 +3811,11 @@ export function lowerOptionalNumber(lowerer, operand, loc, narrowedNode) {
3768
3811
  if (operand.type.kind !== "union" || lowerer.armTag(operand.type.unionId, UNDEFINED_T) < 0)
3769
3812
  return operand;
3770
3813
  const directNumber = lowerer.stripUndefinedArm(operand.type).kind === "f64";
3814
+ if (directNumber) {
3815
+ const scalarRead = tryLowerNumericIndexRead(lowerer, operand, loc);
3816
+ if (scalarRead)
3817
+ return scalarRead;
3818
+ }
3771
3819
  const checkerNumber = narrowedNode !== undefined && lowerer.mapTypeOf(lowerer.typeOf(narrowedNode))?.kind === "f64";
3772
3820
  if (!directNumber && (!checkerNumber || lowerer.armTag(operand.type.unionId, F64) < 0))
3773
3821
  return operand;
@@ -4546,6 +4594,78 @@ function tupleLiteralIndex(node) {
4546
4594
  const n = Number(e.text);
4547
4595
  return Number.isInteger(n) && n >= 0 ? n : null;
4548
4596
  }
4597
+ /** `a[i] op= rhs` over native arrays and byte views. A compound assignment
4598
+ * evaluates the receiver and index, reads the old element, evaluates rhs,
4599
+ * then writes. Capture each stage so a call in the index or rhs cannot
4600
+ * change the target or the value used by the operator. The expression yields
4601
+ * the computed value before a typed array or Buffer coerces it for storage. */
4602
+ export function lowerElementCompound(lowerer, expr, op) {
4603
+ const target = expr.left;
4604
+ fenceNodeModuleMutation(lowerer, target, "assignment");
4605
+ const loc = locOf(expr);
4606
+ const receiverType = lowerer.mapTypeOf(lowerer.typeOf(target.expression));
4607
+ if (receiverType?.kind !== "array" && receiverType?.kind !== "bytes") {
4608
+ lowerer.unsupported("SC1090", target, "compound assignment to non-array elements");
4609
+ }
4610
+ let receiver = lowerer.lowerExpr(target.expression);
4611
+ if (receiver.type.kind === "union" && lowerer.armTag(receiver.type.unionId, UNDEFINED_T) >= 0) {
4612
+ const present = lowerer.stripUndefinedArm(receiver.type);
4613
+ const helper = present.kind === receiverType.kind
4614
+ ? lowerer.narrowedArmHelper(receiver.type.unionId, present, locOf(target.expression))
4615
+ : null;
4616
+ if (helper)
4617
+ receiver = { kind: "call", callee: helper, args: [receiver], type: present, loc: locOf(target.expression) };
4618
+ }
4619
+ if (receiver.type.kind !== receiverType.kind) {
4620
+ lowerer.unsupported("SC1090", target.expression, "compound assignment through a non-native array or byte view");
4621
+ }
4622
+ const index = lowerer.lowerExpr(target.argumentExpression);
4623
+ if (index.type.kind !== "f64")
4624
+ lowerer.unsupported("SC1090", target.argumentExpression, "indexing with non-number keys");
4625
+ const receiverLocal = lowerer.declareHiddenLocal("%compoundArray", receiver.type);
4626
+ const indexLocal = lowerer.declareHiddenLocal("%compoundIndex", F64);
4627
+ const receiverRef = () => varRef(receiverLocal.id, receiver.type, loc);
4628
+ const indexRef = () => varRef(indexLocal.id, F64, loc);
4629
+ const oldValue = receiver.type.kind === "array"
4630
+ ? arrayValueRead(lowerer, receiverRef(), indexRef(), receiver.type.elem, locOf(target))
4631
+ : { kind: "bytesIntrinsic", method: "get", receiver: receiverRef(), args: [indexRef()], type: F64, loc: locOf(target) };
4632
+ const oldLocal = lowerer.declareHiddenLocal("%compoundOld", oldValue.type);
4633
+ const oldRef = () => varRef(oldLocal.id, oldValue.type, loc);
4634
+ const rhs = lowerer.lowerExpr(expr.right);
4635
+ const numericRhs = lowerOptionalNumber(lowerer, rhs, loc);
4636
+ const elementType = receiver.type.kind === "array" ? receiver.type.elem : F64;
4637
+ const valueType = elementType.kind === "union" && lowerer.armTag(elementType.unionId, UNDEFINED_T) >= 0
4638
+ ? lowerer.stripUndefinedArm(elementType)
4639
+ : elementType;
4640
+ let computed;
4641
+ if (op === "+" && valueType.kind === "string") {
4642
+ computed = { kind: "strConcat", left: lowerer.ensureString(oldRef(), target), right: lowerer.ensureString(rhs, expr.right), type: STRING, loc };
4643
+ }
4644
+ else if (valueType.kind === "f64" && numericRhs.type.kind === "f64") {
4645
+ computed = { kind: "bin", op, left: lowerOptionalNumber(lowerer, oldRef(), loc), right: numericRhs, type: F64, loc };
4646
+ }
4647
+ else {
4648
+ lowerer.unsupported("SC1043", expr);
4649
+ }
4650
+ const resultLocal = lowerer.declareHiddenLocal("%compoundResult", computed.type);
4651
+ const resultRef = () => varRef(resultLocal.id, computed.type, loc);
4652
+ const write = receiver.type.kind === "array"
4653
+ ? arrayValueStore(lowerer, receiverRef(), indexRef(), resultRef(), receiver.type.elem, loc)
4654
+ : { kind: "bytesSet", arr: receiverRef(), index: indexRef(), value: resultRef(), loc };
4655
+ return {
4656
+ kind: "seqExpr",
4657
+ stmts: [
4658
+ { kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
4659
+ { kind: "varDecl", localId: indexLocal.id, init: index, loc },
4660
+ { kind: "varDecl", localId: oldLocal.id, init: oldValue, loc },
4661
+ { kind: "varDecl", localId: resultLocal.id, init: computed, loc },
4662
+ write,
4663
+ ],
4664
+ result: resultRef(),
4665
+ type: computed.type,
4666
+ loc,
4667
+ };
4668
+ }
4549
4669
  /** `a[i] = v` in statement position → arraySet (element writes, like local
4550
4670
  * assignment, produce no value in our subset). */
4551
4671
  export function lowerElementWrite(lowerer, expr) {
@@ -4917,6 +5037,18 @@ export function ensureString(lowerer, e, node) {
4917
5037
  if (stringable) {
4918
5038
  return { kind: "toString", operand: e, type: STRING, loc: e.loc };
4919
5039
  }
5040
+ // Runtime-optional locals can retain their stored union after the
5041
+ // checker has narrowed this use to a primitive arm. Validate that
5042
+ // arm before converting it; an unguarded object arm stays fenced.
5043
+ const narrowed = lowerer.mapTypeOf(lowerer.typeOf(node));
5044
+ if (narrowed &&
5045
+ (narrowed.kind === "string" || narrowed.kind === "f64" ||
5046
+ narrowed.kind === "bool" || narrowed.kind === "bigint")) {
5047
+ const helper = lowerer.narrowedArmHelper(e.type.unionId, narrowed, e.loc);
5048
+ if (helper) {
5049
+ return ensureString(lowerer, { kind: "call", callee: helper, args: [e], type: narrowed, loc: e.loc }, node);
5050
+ }
5051
+ }
4920
5052
  lowerer.unsupported("SC1090", node, `string conversions of unions with object arms (${NARROW_FIRST})`);
4921
5053
  }
4922
5054
  if (e.type.kind === "symbol") {
@@ -4968,7 +5100,7 @@ export function ensureString(lowerer, e, node) {
4968
5100
  * neither is observable where the value immediately stringifies — so the
4969
5101
  * span lowers as the argument's own ToString (`new String()` is "").
4970
5102
  * Every other position keeps the wrapper-object constructor fence. */
4971
- function stringWrapperToString(lowerer, node) {
5103
+ export function stringWrapperToString(lowerer, node) {
4972
5104
  let e = node;
4973
5105
  while (ts.isParenthesizedExpression(e))
4974
5106
  e = e.expression;
@@ -4982,7 +5114,11 @@ function stringWrapperToString(lowerer, node) {
4982
5114
  return null;
4983
5115
  if (args.length === 0)
4984
5116
  return { kind: "strLit", value: "", type: STRING, loc: locOf(e) };
4985
- return lowerer.caughtToString(args[0]) ?? lowerer.ensureString(lowerer.lowerExpr(args[0]), args[0]);
5117
+ const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, args[0]);
5118
+ if (undefinedArg)
5119
+ return defaultAfterUndefined(undefinedArg, { kind: "strLit", value: "undefined", type: STRING, loc: locOf(e) });
5120
+ return lowerer.caughtToString(args[0]) ??
5121
+ coerceStringSearchValue(lowerer, lowerer.lowerExpr(args[0]), args[0], locOf(e));
4986
5122
  }
4987
5123
  export function lowerTemplate(lowerer, expr) {
4988
5124
  const loc = locOf(expr);
@@ -5229,7 +5365,14 @@ export function lowerPrefixUnary(lowerer, expr) {
5229
5365
  if (raw.type.kind === "bigint") {
5230
5366
  return { kind: "libCall", fn: "bigint.not", args: [raw], type: BIGINT_T, loc };
5231
5367
  }
5232
- const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand);
5368
+ // A checked-dynamic value retains enough JavaScript structure to
5369
+ // run exact ToNumber, including the object valueOf/toString
5370
+ // protocol. This is the static `any`/JS-lane answer for idioms
5371
+ // such as `while (~index)`, where inference may have widened an
5372
+ // otherwise numeric slot to the checked-dynamic tree.
5373
+ const operand = raw.type.kind === "dyn"
5374
+ ? { kind: "libCall", fn: "dyn.toNumberCoerce", args: [raw], type: F64, loc }
5375
+ : lowerOptionalNumber(lowerer, raw, loc, expr.operand);
5233
5376
  if (operand.type.kind !== "f64")
5234
5377
  lowerer.unsupported("SC1043", expr);
5235
5378
  return { kind: "unary", op: "~", operand, type: F64, loc };
@@ -5367,8 +5510,9 @@ export function lowerBinary(lowerer, expr) {
5367
5510
  // globals, captured/boxed included); the RHS coerces into the binding's
5368
5511
  // type exactly like statement position, and the expression's value is
5369
5512
  // the coerced binding-typed value (representation change only — never
5370
- // observably different from JS's raw-RHS yield). Compound operators,
5371
- // property/element targets, and destructuring targets stay fenced.
5513
+ // observably different from JS's raw-RHS yield). Indexed compounds
5514
+ // use a sequenced read/modify/write; other member targets and
5515
+ // destructuring targets stay fenced.
5372
5516
  if (op === ts.SyntaxKind.EqualsToken && ts.isIdentifier(expr.left)) {
5373
5517
  const target = lowerer.resolveWritable(expr.left);
5374
5518
  if (!target) {
@@ -5380,6 +5524,10 @@ export function lowerBinary(lowerer, expr) {
5380
5524
  lowerer.runtimeOptionalLocals.add(runtimeOptionalRoot);
5381
5525
  return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
5382
5526
  }
5527
+ const indexedCompound = COMPOUND_ASSIGN_OPS[op];
5528
+ if (indexedCompound !== undefined && ts.isElementAccessExpression(expr.left)) {
5529
+ return lowerElementCompound(lowerer, expr, indexedCompound);
5530
+ }
5383
5531
  // `events.defaultMaxListeners = v` — the module-property write
5384
5532
  // Node validates (validateNumber(n, 'defaultMaxListeners', 0)):
5385
5533
  // the value crosses into the checked-dynamic tree and the runtime ladder throws
@@ -5518,23 +5666,10 @@ export function lowerBinary(lowerer, expr) {
5518
5666
  const nullTest = lowerLooseNullCompare(lowerer, expr, loc);
5519
5667
  if (nullTest)
5520
5668
  return nullTest;
5521
- // SAME-KIND loose equality IS strict equality (the spec's ==
5522
- // dispatches to === when both operands share a type): `typeof v ==
5523
- // 'object'`, `n != 0`, `flag == true` all lower exactly. Mixed
5524
- // kinds (where == coerces) keep the fence.
5525
- {
5526
- const negated = op === ts.SyntaxKind.ExclamationEqualsToken;
5527
- const left = lowerer.lowerExpr(expr.left);
5528
- const right = lowerer.lowerExpr(expr.right);
5529
- if (left.type.kind === "string" && right.type.kind === "string") {
5530
- return { kind: "strEq", negated, left, right, type: BOOL, loc };
5531
- }
5532
- if ((left.type.kind === "f64" && right.type.kind === "f64") ||
5533
- (left.type.kind === "bool" && right.type.kind === "bool")) {
5534
- return { kind: "bin", op: negated ? "!==" : "===", left, right, type: BOOL, loc };
5535
- }
5536
- }
5537
- lowerer.unsupported("SC1040", expr);
5669
+ const loose = lowerAbstractEquality(lowerer, lowerer.lowerExpr(expr.left), lowerer.lowerExpr(expr.right), op === ts.SyntaxKind.ExclamationEqualsToken, loc);
5670
+ if (loose)
5671
+ return loose;
5672
+ lowerer.unsupported("SC1040", expr, undefined, "object-to-primitive loose equality can call user-defined valueOf/toString methods — compare explicit primitive conversions instead");
5538
5673
  }
5539
5674
  if (op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken) {
5540
5675
  if (op === ts.SyntaxKind.BarBarToken) {
@@ -5558,6 +5693,15 @@ export function lowerBinary(lowerer, expr) {
5558
5693
  if (left.kind === "unitLit") {
5559
5694
  return op === ts.SyntaxKind.BarBarToken ? right : left;
5560
5695
  }
5696
+ // JS objects and symbols are always truthy. In `ref || fallback`
5697
+ // the fallback is therefore unreachable and the result is the
5698
+ // already-evaluated left value — the browser-fallback idiom used by
5699
+ // packages for `process.argv || []` and `process.env || {}`. The
5700
+ // operand itself remains in the IR, preserving reads/calls that
5701
+ // produce the reference; only the unreachable right lowering drops.
5702
+ if (op === ts.SyntaxKind.BarBarToken && REF_TRUTHY_KINDS.has(left.type.kind)) {
5703
+ return left;
5704
+ }
5561
5705
  if (left.type.kind === "dyn" || right.type.kind === "dyn") {
5562
5706
  // A checked-dynamic operand (`fn.name || '<anonymous>'` —
5563
5707
  // test/common's _mustCallInner): both sides live in the checked-dynamic tree and
@@ -6253,6 +6397,9 @@ export function lowerBinary(lowerer, expr) {
6253
6397
  if (idLeft.type.kind === "classval" && idRight.type.kind === "classval") {
6254
6398
  return { kind: "bin", op: negated ? "!==" : "===", left: idLeft, right: idRight, type: BOOL, loc };
6255
6399
  }
6400
+ if (idLeft.type.kind === "moduleNs" && idRight.type.kind === "moduleNs") {
6401
+ return { kind: "bin", op: negated ? "!==" : "===", left: idLeft, right: idRight, type: BOOL, loc };
6402
+ }
6256
6403
  if ((idLeft.type.kind === "array" ||
6257
6404
  idLeft.type.kind === "map" ||
6258
6405
  idLeft.type.kind === "set" ||
@@ -7359,10 +7506,10 @@ function lowerInExpression(lowerer, expr, loc) {
7359
7506
  // Compile-time-known STRING keys fold — literals, and the same
7360
7507
  // const/enum-literal and template folding computed property keys get
7361
7508
  // (foldedStringKeyOf); runtime-valued keys keep the fence.
7362
- // NUMERIC literal keys answer on ARRAY receivers: dense arrays hold
7363
- // exactly the indices [0, length), so `3 in xs` is a length test (and
7364
- // a negative/fractional literal is a constant miss — the receiver
7365
- // still evaluates once through its own length read).
7509
+ // NUMERIC literal keys answer on ARRAY receivers through the shared
7510
+ // presence query. Arrays retain holes independently from length, and
7511
+ // noncanonical numeric keys live in their ordinary-property table, so
7512
+ // a length comparison is not an honest `in` answer.
7366
7513
  {
7367
7514
  let kNode = expr.left;
7368
7515
  while (ts.isParenthesizedExpression(kNode))
@@ -7370,12 +7517,14 @@ function lowerInExpression(lowerer, expr, loc) {
7370
7517
  if (ts.isNumericLiteral(kNode) && lowerer.mapTypeOf(lowerer.typeOf(expr.right))?.kind === "array") {
7371
7518
  const recvArr = lowerer.lowerExpr(expr.right);
7372
7519
  if (recvArr.type.kind === "array") {
7373
- const len = { kind: "arrIntrinsic", method: "length", receiver: recvArr, args: [], type: F64, loc };
7374
7520
  const n = Number(kNode.text);
7375
- if (Number.isInteger(n) && n >= 0) {
7376
- return { kind: "bin", op: "<", left: { kind: "numLit", value: n, type: F64, loc }, right: len, type: BOOL, loc };
7377
- }
7378
- return { kind: "bin", op: "<", left: len, right: { kind: "numLit", value: 0, type: F64, loc }, type: BOOL, loc };
7521
+ return {
7522
+ kind: "arrayHas",
7523
+ arr: recvArr,
7524
+ index: { kind: "numLit", value: n, type: F64, loc },
7525
+ type: BOOL,
7526
+ loc,
7527
+ };
7379
7528
  }
7380
7529
  }
7381
7530
  }
@@ -8695,7 +8844,20 @@ export function fieldGetExpr(lowerer, target, loc, blame) {
8695
8844
  // construction), typed as the index value armed with undefined under
8696
8845
  // noUncheckedIndexedAccess (mirroring lowerRecordKeyRead).
8697
8846
  if (target.container === "recordOvf") {
8698
- let t = target.fieldType;
8847
+ let obj = target.obj;
8848
+ if (obj.type.kind === "union" && ts.isPropertyAccessExpression(blame)) {
8849
+ const present = lowerer.stripUndefinedArm(obj.type);
8850
+ if (present.kind === "record") {
8851
+ obj = lowerer.runtimeOptionalPropertyReceiver(blame.expression, obj, present, target.field) ?? obj;
8852
+ }
8853
+ }
8854
+ if (obj.type.kind !== "record") {
8855
+ lowerer.unsupported("SC1090", blame, `reading '${target.field}' on a non-record receiver (narrow first)`);
8856
+ }
8857
+ const shape = lowerer.shapes.get(obj.type.shapeId);
8858
+ if (!shape?.indexValue)
8859
+ lowerer.unsupported("SC1090", blame, `reading '${target.field}' on a record without an index signature`);
8860
+ let t = shape.indexValue;
8699
8861
  if (lowerer.program.getCompilerOptions().noUncheckedIndexedAccess) {
8700
8862
  const armed = lowerer.withUndefinedArmOf(t);
8701
8863
  if (!armed)
@@ -8704,8 +8866,8 @@ export function fieldGetExpr(lowerer, target, loc, blame) {
8704
8866
  }
8705
8867
  return {
8706
8868
  kind: "recordKeyGet",
8707
- obj: target.obj,
8708
- shapeId: target.shapeId,
8869
+ obj,
8870
+ shapeId: obj.type.shapeId,
8709
8871
  key: { kind: "strLit", value: target.field, type: STRING, loc },
8710
8872
  overflowOnly: true,
8711
8873
  type: t,
@@ -9011,32 +9173,40 @@ function lowerStreamObjectProperty(lowerer, expr) {
9011
9173
  return lowerStreamProperty(lowerer, expr, info);
9012
9174
  }
9013
9175
  /** The primitive-constructor closure (`String`/`Number`/`Boolean` as a
9014
- * VALUE): interns one synthesized module function per constructor —
9015
- * `%builtin.String` et al. — and returns the zero-capture closure over it.
9016
- * The body IS the string-coercion the type mapping promised
9017
- * (`(value: string) => primitive`): String is identity, Number the ECMA
9018
- * StringToNumber (num.fromString — the same runtime call the direct
9019
- * `Number(s)` lowering emits), Boolean the emptiness test. Interning makes
9020
- * every reference the SAME immortal closure, so `opt.type === String`
9021
- * compares like JS function identity. */
9022
- function primitiveCtorClosure(lowerer, name, loc) {
9176
+ * VALUE): interns one synthesized module function per constructor/ABI and
9177
+ * returns the zero-capture closure over it. Typed sources use the mapped
9178
+ * `(value: string) => primitive` signature; a recognized JavaScript
9179
+ * concise-arrow selector (`() => String`, the picocolors fallback) takes
9180
+ * dyn so its ordinary coercion signature survives inference residue. The
9181
+ * dyn bodies run exact ToString/ToNumber/ToBoolean, including object hooks.
9182
+ * Interning makes every reference under one ABI the SAME immortal closure,
9183
+ * so `opt.type === String` compares like JS function identity. */
9184
+ function primitiveCtorClosure(lowerer, name, loc, dynamicInput) {
9023
9185
  const ret = name === "String" ? STRING : name === "Number" ? F64 : BOOL;
9024
- const fnT = funcOf([STRING], ret);
9025
- let fnName = lowerer.primitiveCtorFns.get(name);
9186
+ const input = dynamicInput ? DYN : STRING;
9187
+ const fnT = funcOf([input], ret);
9188
+ const key = `${name}:${dynamicInput ? "dyn" : "string"}`;
9189
+ let fnName = lowerer.primitiveCtorFns.get(key);
9026
9190
  if (!fnName) {
9027
- fnName = `%builtin.${name}`;
9028
- lowerer.primitiveCtorFns.set(name, fnName);
9029
- const s = { kind: "varRef", localId: "v.0", type: STRING, loc };
9030
- const value = name === "String"
9031
- ? s
9032
- : name === "Number"
9033
- ? { kind: "libCall", fn: "num.fromString", args: [s], type: F64, loc }
9034
- : { kind: "strEq", negated: true, left: s, right: { kind: "strLit", value: "", type: STRING, loc }, type: BOOL, loc };
9191
+ fnName = `%builtin.${name}${dynamicInput ? ".dyn" : ""}`;
9192
+ lowerer.primitiveCtorFns.set(key, fnName);
9193
+ const v = { kind: "varRef", localId: "v.0", type: input, loc };
9194
+ const value = dynamicInput
9195
+ ? name === "String"
9196
+ ? { kind: "libCall", fn: "dyn.toStringCoerce", args: [v], type: STRING, loc }
9197
+ : name === "Number"
9198
+ ? { kind: "libCall", fn: "dyn.toNumberCoerce", args: [v], type: F64, loc }
9199
+ : { kind: "dynTest", test: "truthy", value: v, type: BOOL, loc }
9200
+ : name === "String"
9201
+ ? v
9202
+ : name === "Number"
9203
+ ? { kind: "libCall", fn: "num.fromString", args: [v], type: F64, loc }
9204
+ : { kind: "strEq", negated: true, left: v, right: { kind: "strLit", value: "", type: STRING, loc }, type: BOOL, loc };
9035
9205
  const fn = {
9036
9206
  name: fnName,
9037
- params: [{ localId: "v.0", name: "value", type: STRING }],
9207
+ params: [{ localId: "v.0", name: "value", type: input }],
9038
9208
  returnType: ret,
9039
- locals: [{ id: "v.0", name: "value", type: STRING, mutable: false }],
9209
+ locals: [{ id: "v.0", name: "value", type: input, mutable: false }],
9040
9210
  body: [{ kind: "return", value, loc }],
9041
9211
  loc,
9042
9212
  };