@scriptc/compiler 0.1.1 → 0.1.3

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 (279) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +104 -31
  2. package/ambient/scriptc.d.ts +2 -1
  3. package/dist/backend/build-cache.js.map +1 -1
  4. package/dist/backend/c/async.d.ts +5 -0
  5. package/dist/backend/c/async.js +81 -2
  6. package/dist/backend/c/async.js.map +1 -1
  7. package/dist/backend/c/c-emitter.d.ts +25 -10
  8. package/dist/backend/c/c-emitter.js +74 -22
  9. package/dist/backend/c/c-emitter.js.map +1 -1
  10. package/dist/backend/c/exprs.d.ts +8 -1
  11. package/dist/backend/c/exprs.js +371 -18
  12. package/dist/backend/c/exprs.js.map +1 -1
  13. package/dist/backend/c/island.js.map +1 -1
  14. package/dist/backend/c/may-throw.js.map +1 -1
  15. package/dist/backend/c/shapes.js +15 -13
  16. package/dist/backend/c/shapes.js.map +1 -1
  17. package/dist/backend/c/stmts.d.ts +3 -5
  18. package/dist/backend/c/stmts.js +58 -17
  19. package/dist/backend/c/stmts.js.map +1 -1
  20. package/dist/backend/c/types.d.ts +4 -0
  21. package/dist/backend/c/types.js +18 -1
  22. package/dist/backend/c/types.js.map +1 -1
  23. package/dist/backend/c/types.test.js +8 -1
  24. package/dist/backend/c/types.test.js.map +1 -1
  25. package/dist/backend/c/walkers.js +63 -16
  26. package/dist/backend/c/walkers.js.map +1 -1
  27. package/dist/backend/external-c.js.map +1 -1
  28. package/dist/backend/json-literal.d.ts +7 -0
  29. package/dist/backend/json-literal.js +17 -0
  30. package/dist/backend/json-literal.js.map +1 -0
  31. package/dist/backend/json-literal.test.d.ts +1 -0
  32. package/dist/backend/json-literal.test.js +17 -0
  33. package/dist/backend/json-literal.test.js.map +1 -0
  34. package/dist/backend/link-plan.d.ts +1 -1
  35. package/dist/backend/link-plan.js +5 -1
  36. package/dist/backend/link-plan.js.map +1 -1
  37. package/dist/backend/linker.js.map +1 -1
  38. package/dist/backend/llvm/blocks.js.map +1 -1
  39. package/dist/backend/llvm/classes.js +8 -7
  40. package/dist/backend/llvm/classes.js.map +1 -1
  41. package/dist/backend/llvm/common.d.ts +4 -0
  42. package/dist/backend/llvm/common.js +6 -0
  43. package/dist/backend/llvm/common.js.map +1 -1
  44. package/dist/backend/llvm/common.test.d.ts +1 -0
  45. package/dist/backend/llvm/common.test.js +9 -0
  46. package/dist/backend/llvm/common.test.js.map +1 -0
  47. package/dist/backend/llvm/dyn.d.ts +4 -0
  48. package/dist/backend/llvm/dyn.js +80 -16
  49. package/dist/backend/llvm/dyn.js.map +1 -1
  50. package/dist/backend/llvm/emitter.js +132 -25
  51. package/dist/backend/llvm/emitter.js.map +1 -1
  52. package/dist/backend/llvm/expr-async.js +85 -3
  53. package/dist/backend/llvm/expr-async.js.map +1 -1
  54. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  55. package/dist/backend/llvm/expr-calls.js.map +1 -1
  56. package/dist/backend/llvm/expr-containers.js.map +1 -1
  57. package/dist/backend/llvm/expr-context.d.ts +5 -0
  58. package/dist/backend/llvm/expr-control.js.map +1 -1
  59. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  60. package/dist/backend/llvm/expr-dynamic.js +15 -3
  61. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  62. package/dist/backend/llvm/expr-island.js.map +1 -1
  63. package/dist/backend/llvm/expr-primitives.js +8 -0
  64. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  65. package/dist/backend/llvm/expr-records.js +3 -3
  66. package/dist/backend/llvm/expr-records.js.map +1 -1
  67. package/dist/backend/llvm/expr-stream-bridges.js +222 -10
  68. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  69. package/dist/backend/llvm/expr-stream-callbacks.d.ts +3 -0
  70. package/dist/backend/llvm/expr-stream-callbacks.js +53 -0
  71. package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
  72. package/dist/backend/llvm/lib-dispatch.js +24 -0
  73. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  74. package/dist/backend/llvm/lib-process.js +50 -5
  75. package/dist/backend/llvm/lib-process.js.map +1 -1
  76. package/dist/backend/llvm/lib-shared.js +64 -0
  77. package/dist/backend/llvm/lib-shared.js.map +1 -1
  78. package/dist/backend/llvm/lib-stream.js +6 -3
  79. package/dist/backend/llvm/lib-stream.js.map +1 -1
  80. package/dist/backend/llvm/shapes.js +10 -8
  81. package/dist/backend/llvm/shapes.js.map +1 -1
  82. package/dist/backend/llvm/split.js.map +1 -1
  83. package/dist/backend/llvm/unsupported.js.map +1 -1
  84. package/dist/backend/llvm/walkers.js +7 -5
  85. package/dist/backend/llvm/walkers.js.map +1 -1
  86. package/dist/backend/native-codegen.js.map +1 -1
  87. package/dist/backend/native-codegen.test.js +2 -0
  88. package/dist/backend/native-codegen.test.js.map +1 -1
  89. package/dist/backend/native-link-info.d.ts +2 -1
  90. package/dist/backend/native-link-info.js +4 -0
  91. package/dist/backend/native-link-info.js.map +1 -1
  92. package/dist/backend/native-link-info.test.js +24 -1
  93. package/dist/backend/native-link-info.test.js.map +1 -1
  94. package/dist/backend/native-toolchain.d.ts +8 -6
  95. package/dist/backend/native-toolchain.js +28 -13
  96. package/dist/backend/native-toolchain.js.map +1 -1
  97. package/dist/backend/native-toolchain.test.js +36 -1
  98. package/dist/backend/native-toolchain.test.js.map +1 -1
  99. package/dist/backend/runtime-pack-targets.test.js +1 -1
  100. package/dist/backend/runtime-pack-targets.test.js.map +1 -1
  101. package/dist/backend/runtime-pack.js.map +1 -1
  102. package/dist/backend/runtime-pack.test.js +1 -0
  103. package/dist/backend/runtime-pack.test.js.map +1 -1
  104. package/dist/backend/targets.d.ts +5 -0
  105. package/dist/backend/targets.js +9 -0
  106. package/dist/backend/targets.js.map +1 -1
  107. package/dist/backend/targets.test.js +6 -1
  108. package/dist/backend/targets.test.js.map +1 -1
  109. package/dist/backend/vendor-archives.js.map +1 -1
  110. package/dist/compat/fetch-profile.js +4 -2
  111. package/dist/compat/fetch-profile.js.map +1 -1
  112. package/dist/coverage/report.js.map +1 -1
  113. package/dist/coverage/surface-manifest.js +13 -0
  114. package/dist/coverage/surface-manifest.js.map +1 -1
  115. package/dist/diagnostics/diagnostic.d.ts +6 -5
  116. package/dist/diagnostics/diagnostic.js +10 -9
  117. package/dist/diagnostics/diagnostic.js.map +1 -1
  118. package/dist/diagnostics/render.js.map +1 -1
  119. package/dist/executable/executable-cache.d.ts +1 -0
  120. package/dist/executable/executable-cache.js +1 -0
  121. package/dist/executable/executable-cache.js.map +1 -1
  122. package/dist/executable/executable-cache.test.js +1 -0
  123. package/dist/executable/executable-cache.test.js.map +1 -1
  124. package/dist/ffi/ffi-manifest.js.map +1 -1
  125. package/dist/frontend/cjs-lexer.js.map +1 -1
  126. package/dist/frontend/dts-paths.js.map +1 -1
  127. package/dist/frontend/input-tracker.d.ts +10 -0
  128. package/dist/frontend/input-tracker.js +26 -3
  129. package/dist/frontend/input-tracker.js.map +1 -1
  130. package/dist/frontend/input-tracker.test.js +8 -1
  131. package/dist/frontend/input-tracker.test.js.map +1 -1
  132. package/dist/frontend/lib-contract.js.map +1 -1
  133. package/dist/frontend/lowering/array-values.d.ts +4 -0
  134. package/dist/frontend/lowering/array-values.js +18 -2
  135. package/dist/frontend/lowering/array-values.js.map +1 -1
  136. package/dist/frontend/lowering/callback-arg.js.map +1 -1
  137. package/dist/frontend/lowering/containers/bytes.d.ts +31 -0
  138. package/dist/frontend/lowering/containers/bytes.js +1105 -0
  139. package/dist/frontend/lowering/containers/bytes.js.map +1 -0
  140. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +17 -0
  141. package/dist/frontend/lowering/containers/string-and-regexp.js +345 -0
  142. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -0
  143. package/dist/frontend/lowering/expressions/evaluation-safety.d.ts +13 -0
  144. package/dist/frontend/lowering/expressions/evaluation-safety.js +76 -0
  145. package/dist/frontend/lowering/expressions/evaluation-safety.js.map +1 -0
  146. package/dist/frontend/lowering/expressions/object-literals.d.ts +44 -0
  147. package/dist/frontend/lowering/expressions/object-literals.js +1815 -0
  148. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -0
  149. package/dist/frontend/lowering/expressions/optional-chains.d.ts +31 -0
  150. package/dist/frontend/lowering/expressions/optional-chains.js +382 -0
  151. package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -0
  152. package/dist/frontend/lowering/expressions/try-lower-expression.d.ts +7 -0
  153. package/dist/frontend/lowering/expressions/try-lower-expression.js +26 -0
  154. package/dist/frontend/lowering/expressions/try-lower-expression.js.map +1 -0
  155. package/dist/frontend/lowering/lib-boundary.js +7 -2
  156. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  157. package/dist/frontend/lowering/lower-assert.js +16 -10
  158. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  159. package/dist/frontend/lowering/lower-async-iteration.d.ts +7 -0
  160. package/dist/frontend/lowering/lower-async-iteration.js +657 -0
  161. package/dist/frontend/lowering/lower-async-iteration.js.map +1 -0
  162. package/dist/frontend/lowering/lower-builtins.d.ts +40 -21
  163. package/dist/frontend/lowering/lower-builtins.js +611 -115
  164. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  165. package/dist/frontend/lowering/lower-calls.d.ts +21 -10
  166. package/dist/frontend/lowering/lower-calls.js +516 -75
  167. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  168. package/dist/frontend/lowering/lower-classes.d.ts +7 -13
  169. package/dist/frontend/lowering/lower-classes.js +128 -35
  170. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  171. package/dist/frontend/lowering/lower-comptime.js.map +1 -1
  172. package/dist/frontend/lowering/lower-containers.d.ts +1 -47
  173. package/dist/frontend/lowering/lower-containers.js +147 -1475
  174. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  175. package/dist/frontend/lowering/lower-event-emitter.js +51 -5
  176. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  177. package/dist/frontend/lowering/lower-exprs.d.ts +0 -78
  178. package/dist/frontend/lowering/lower-exprs.js +503 -2353
  179. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  180. package/dist/frontend/lowering/lower-generators.d.ts +18 -3
  181. package/dist/frontend/lowering/lower-generators.js +145 -9
  182. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  183. package/dist/frontend/lowering/lower-inspect.js +10 -3
  184. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  185. package/dist/frontend/lowering/lower-island.d.ts +6 -0
  186. package/dist/frontend/lowering/lower-island.js +37 -5
  187. package/dist/frontend/lowering/lower-island.js.map +1 -1
  188. package/dist/frontend/lowering/lower-modules.js +60 -15
  189. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  190. package/dist/frontend/lowering/lower-node-module.d.ts +18 -0
  191. package/dist/frontend/lowering/lower-node-module.js +302 -0
  192. package/dist/frontend/lowering/lower-node-module.js.map +1 -0
  193. package/dist/frontend/lowering/lower-server.js +1 -1
  194. package/dist/frontend/lowering/lower-server.js.map +1 -1
  195. package/dist/frontend/lowering/lower-stmts.d.ts +10 -28
  196. package/dist/frontend/lowering/lower-stmts.js +542 -259
  197. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  198. package/dist/frontend/lowering/lower-stream.js +4 -4
  199. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  200. package/dist/frontend/lowering/lowerer.d.ts +41 -38
  201. package/dist/frontend/lowering/lowerer.js +148 -62
  202. package/dist/frontend/lowering/lowerer.js.map +1 -1
  203. package/dist/frontend/lowering/optional-arguments.d.ts +9 -0
  204. package/dist/frontend/lowering/optional-arguments.js +57 -0
  205. package/dist/frontend/lowering/optional-arguments.js.map +1 -0
  206. package/dist/frontend/lowering/surfaces.d.ts +5 -3
  207. package/dist/frontend/lowering/surfaces.js +33 -13
  208. package/dist/frontend/lowering/surfaces.js.map +1 -1
  209. package/dist/frontend/npm-static-declarations.d.ts +35 -0
  210. package/dist/frontend/npm-static-declarations.js +519 -0
  211. package/dist/frontend/npm-static-declarations.js.map +1 -0
  212. package/dist/frontend/npm-static-declarations.test.d.ts +1 -0
  213. package/dist/frontend/npm-static-declarations.test.js +132 -0
  214. package/dist/frontend/npm-static-declarations.test.js.map +1 -0
  215. package/dist/frontend/npm-static.d.ts +2 -0
  216. package/dist/frontend/npm-static.js +26 -10
  217. package/dist/frontend/npm-static.js.map +1 -1
  218. package/dist/frontend/npm.d.ts +60 -0
  219. package/dist/frontend/npm.js +38 -4
  220. package/dist/frontend/npm.js.map +1 -1
  221. package/dist/frontend/npm.test.d.ts +1 -0
  222. package/dist/frontend/npm.test.js +50 -0
  223. package/dist/frontend/npm.test.js.map +1 -0
  224. package/dist/frontend/program.d.ts +3 -2
  225. package/dist/frontend/program.js +513 -70
  226. package/dist/frontend/program.js.map +1 -1
  227. package/dist/frontend/resolve.d.ts +26 -3
  228. package/dist/frontend/resolve.js +166 -29
  229. package/dist/frontend/resolve.js.map +1 -1
  230. package/dist/frontend/resolve.test.js +24 -1
  231. package/dist/frontend/resolve.test.js.map +1 -1
  232. package/dist/frontend/runtime-resolve.d.ts +22 -0
  233. package/dist/frontend/runtime-resolve.js +101 -0
  234. package/dist/frontend/runtime-resolve.js.map +1 -0
  235. package/dist/frontend/runtime-resolve.test.d.ts +1 -0
  236. package/dist/frontend/runtime-resolve.test.js +18 -0
  237. package/dist/frontend/runtime-resolve.test.js.map +1 -0
  238. package/dist/frontend/ts7/adapter.js +12 -13
  239. package/dist/frontend/ts7/adapter.js.map +1 -1
  240. package/dist/frontend/ts7/ast.js.map +1 -1
  241. package/dist/frontend/ts7/census-check.js +3 -3
  242. package/dist/frontend/ts7/census-check.js.map +1 -1
  243. package/dist/frontend/ts7/checker.d.ts +3 -0
  244. package/dist/frontend/ts7/checker.js +12 -3
  245. package/dist/frontend/ts7/checker.js.map +1 -1
  246. package/dist/frontend/ts7/enums.js.map +1 -1
  247. package/dist/frontend/ts7/program-adapter.js +42 -3
  248. package/dist/frontend/ts7/program-adapter.js.map +1 -1
  249. package/dist/frontend/type-mapper.js +171 -16
  250. package/dist/frontend/type-mapper.js.map +1 -1
  251. package/dist/index.js +11 -5
  252. package/dist/index.js.map +1 -1
  253. package/dist/ir/analysis.js +1 -0
  254. package/dist/ir/analysis.js.map +1 -1
  255. package/dist/ir/ir.d.ts +125 -52
  256. package/dist/ir/ir.js +136 -7
  257. package/dist/ir/ir.js.map +1 -1
  258. package/dist/ir/serialize.d.ts +1 -1
  259. package/dist/ir/serialize.js +1 -1
  260. package/dist/ir/validate.js +120 -63
  261. package/dist/ir/validate.js.map +1 -1
  262. package/dist/library/cache-primitives.js.map +1 -1
  263. package/dist/library/cache-reset.test.js.map +1 -1
  264. package/dist/library/compiler-self-identity.js.map +1 -1
  265. package/dist/library/int-infer.js.map +1 -1
  266. package/dist/library/int-infer.test.js +4 -4
  267. package/dist/library/int-infer.test.js.map +1 -1
  268. package/dist/library/library-cache.d.ts +1 -0
  269. package/dist/library/library-cache.js +1 -0
  270. package/dist/library/library-cache.js.map +1 -1
  271. package/dist/library/library-cache.test.js +18 -5
  272. package/dist/library/library-cache.test.js.map +1 -1
  273. package/dist/library/library-profile.js.map +1 -1
  274. package/dist/library/sidecar-validate.js.map +1 -1
  275. package/dist/library/sidecar.js.map +1 -1
  276. package/dist/startup-cache.js.map +1 -1
  277. package/dist/wasi-paths.js.map +1 -1
  278. package/package.json +18 -18
  279. package/surface-manifest.json +91 -20
@@ -1,96 +1,21 @@
1
1
  import { InternalCompilerError } from "../../errors.js";
2
2
  /* Container-surface call lowering: array methods (including the HOF family
3
3
  * map/filter/forEach with their synthesized helper functions), Map/Set
4
- * methods with Node-exact forEach desugaring, and the string and regex
5
- * method surfaces. */
4
+ * methods with Node-exact forEach desugaring, iterator helpers, and indexed
5
+ * record operations. */
6
6
  import * as ts from "../ts7/adapter.js";
7
- import { BOOL, BYTES_U8, CAUGHT, DYN, F64, JSVAL, STRING, UNDEFINED_T, VOID, arrayOf, bytesOf, funcOf, isRefCounted, isSupportedArrayElem, isSupportedIndexValue, isUnitType, typeEquals } from "../../ir/ir.js";
8
- import { ARRAY_METHODS, MAP_METHODS, SET_COMBINE_METHODS, SET_METHODS, STR_METHODS } from "./surfaces.js";
9
- import { droppableStatic, isRequireMainFilename, lowerDynObjectLiteral, probeLower, pureReemittable } from "./lower-exprs.js";
7
+ import { BOOL, CAUGHT, DYN, F64, JSVAL, STRING, UNDEFINED_T, VOID, arrayOf, funcOf, isRefCounted, isSupportedArrayElem, isSupportedIndexValue, isUnitType, typeEquals } from "../../ir/ir.js";
8
+ import { ARRAY_METHODS, MAP_METHODS, SET_COMBINE_METHODS, SET_METHODS } from "./surfaces.js";
9
+ import { tryLowerExpression } from "./expressions/try-lower-expression.js";
10
+ import { isSafeToRepeat } from "./expressions/evaluation-safety.js";
10
11
  import { forOfVarTarget, lowerDestructuringAssign } from "./lower-stmts.js";
11
12
  import { isJsSourceFile, locOf } from "../program.js";
12
13
  import { islandPrimitiveExit, lowerDynDispatchMethodCall } from "./lower-calls.js";
13
- import { buildArraySortFn, buildBytesSortFn } from "./lower-array-sort.js";
14
- import { arrayValueRead, arrayValueStore, arrayValueType } from "./array-values.js";
14
+ import { buildArraySortFn } from "./lower-array-sort.js";
15
+ import { arrayIndexPresent, arrayValueRead, arrayValueStore, arrayValueType, currentArrayIndexPresent } from "./array-values.js";
15
16
  import { typeKey } from "../type-mapper.js";
16
- import { dynUndefinedExpr, own } from "./lowerer.js";
17
17
  import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
18
- /** Lower an expression whose checker type is statically `undefined`/`void`.
19
- * Optional builtin arguments use this before their ordinary expected-type
20
- * coercion so every equivalent spelling (`undefined`, `void 0`, a typed
21
- * binding, or an effectful call returning undefined) selects the default.
22
- * An effectful `void e` is exact in this context: evaluate e, then produce
23
- * undefined, instead of hitting value-position void's general fence. */
24
- function lowerStaticallyUndefinedArg(lowerer, node) {
25
- const peelErasableWrappers = (value) => {
26
- let expr = value;
27
- while (ts.isParenthesizedExpression(expr) ||
28
- ts.isAsExpression(expr) ||
29
- ts.isTypeAssertion(expr) ||
30
- ts.isSatisfiesExpression(expr)) {
31
- expr = expr.expression;
32
- }
33
- return expr;
34
- };
35
- let expr = node;
36
- while (ts.isParenthesizedExpression(expr))
37
- expr = expr.expression;
38
- if ((lowerer.typeOf(expr).flags &
39
- (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) ===
40
- 0) {
41
- return null;
42
- }
43
- // TypeScript erases `as`, angle-bracket assertions, and `satisfies`.
44
- // Peel them only for the void recognition: a non-void assertion still
45
- // lowers through the ordinary path below, preserving the checked-dynamic
46
- // cast discipline. Nested voids add no effects of their own, so the one
47
- // innermost operand carries the complete runtime evaluation.
48
- expr = peelErasableWrappers(expr);
49
- let sawVoid = false;
50
- while (ts.isVoidExpression(expr)) {
51
- sawVoid = true;
52
- expr = peelErasableWrappers(expr.expression);
53
- }
54
- if (sawVoid) {
55
- // The caller discards this token before producing the parameter
56
- // default, so the operand itself carries exactly the required effects;
57
- // no bare undefined value needs to enter the IR.
58
- return lowerer.lowerExpr(expr);
59
- }
60
- return lowerer.lowerExpr(node);
61
- }
62
- /** Preserve a statically-undefined argument's effects, then answer the
63
- * optional parameter's already-lowered default value. */
64
- function defaultAfterUndefined(value, defaultValue) {
65
- if (droppableStatic(value))
66
- return defaultValue;
67
- return {
68
- kind: "seqExpr",
69
- stmts: [{ kind: "exprStmt", expr: value, loc: value.loc }],
70
- result: defaultValue,
71
- type: defaultValue.type,
72
- loc: value.loc,
73
- };
74
- }
75
- function lowerOptionalDefaultArg(lowerer, node, expected, defaultValue) {
76
- const undefinedArg = lowerStaticallyUndefinedArg(lowerer, node);
77
- if (undefinedArg)
78
- return defaultAfterUndefined(undefinedArg, defaultValue);
79
- const value = lowerer.lowerExpr(node);
80
- if (value.type.kind === "union") {
81
- const def = lowerer.unions.get(value.type.unionId);
82
- if (def?.arms.length === 2 &&
83
- def.arms.some((arm) => arm.kind === "undefinedT") &&
84
- def.arms.some((arm) => typeEquals(arm, expected))) {
85
- // A runtime optional argument uses its API default only on the
86
- // undefined arm. The exact two-arm gate deliberately excludes null:
87
- // optional parameters do not admit it, and an asserted null must keep
88
- // the ordinary checked-narrow failure instead of defaulting.
89
- return { kind: "nullish", left: value, right: defaultValue, type: expected, loc: value.loc };
90
- }
91
- }
92
- return lowerer.coerceInto(node, value, expected);
93
- }
18
+ import { lowerOptionalArgument } from "./optional-arguments.js";
94
19
  /** Build the argument array for a mutating/copying operation while keeping
95
20
  * the receiver's scalar payload ABI. A missing read is represented as the
96
21
  * present UNDEFINED state in this temporary, so push/unshift and toSpliced
@@ -117,23 +42,6 @@ function lowerArrayValueItems(lowerer, values, elem, arrType, loc) {
117
42
  }
118
43
  return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
119
44
  }
120
- function lowerSplitLimitArg(lowerer, node, loc) {
121
- const defaultValue = { kind: "numLit", value: 4294967295, type: F64, loc };
122
- return node ? lowerOptionalDefaultArg(lowerer, node, F64, defaultValue) : defaultValue;
123
- }
124
- /** Extract the checker-proven receiver arm from a runtime-optional array
125
- * read. The receiver is stabilized once before its tag is tested, and a
126
- * missing value throws the same property-read TypeError Node throws before
127
- * evaluating method arguments. */
128
- function lowerMethodReceiver(lowerer, node, expected, member) {
129
- const lowered = lowerer.lowerExpr(node);
130
- const optional = lowerer.runtimeOptionalPropertyReceiver(node, lowered, expected, member);
131
- if (optional)
132
- return optional;
133
- if (typeEquals(lowered.type, expected))
134
- return lowered;
135
- return lowerer.coerceInto(node, lowered, expected);
136
- }
137
45
  /** Callback-driven array producers bypass mapType's ordinary T[] mapping:
138
46
  * the callback is lowered first, then the helper's result array is built
139
47
  * directly from its IR return type. Fence element kinds ScrArr cannot hold
@@ -160,6 +68,9 @@ export function lowerArrayMethodCall(lowerer, call, access) {
160
68
  if (!ARRAY_METHODS.has(name) && name !== "sort" && name !== "shift" && name !== "splice")
161
69
  return null;
162
70
  let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
71
+ const checkerReceiver = lowerer.checker.getTypeAtLocation(access.expression);
72
+ const implicitArrayReceiver = receiverIr?.kind === "array" &&
73
+ (checkerReceiver.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown) || lowerer.checkerAnyArrayType(checkerReceiver));
163
74
  // A checker-`any[]` receiver (the readonly-array Array.isArray quirk)
164
75
  // whose VALUE lowers to a real static array (maybeNarrow's isArray
165
76
  // bridge): ride the ordinary tables on the lowered element type — the
@@ -168,7 +79,10 @@ export function lowerArrayMethodCall(lowerer, call, access) {
168
79
  // the isStdlibMember gate is skipped for probed receivers (nothing
169
80
  // user-declared can shadow a method on a value the checker calls any).
170
81
  let probedUntyped = false;
171
- if (!receiverIr &&
82
+ if (implicitArrayReceiver)
83
+ probedUntyped = true;
84
+ if ((receiverIr === null || receiverIr.kind === "dyn" ||
85
+ (receiverIr.kind === "array" && receiverIr.elem.kind === "dyn")) &&
172
86
  (lowerer.checkerAnyArray(access.expression) ||
173
87
  // A checker-`any` CHAIN whose value lowers to a real static array
174
88
  // (`context.stack.split('\n').slice(2)` — the dyn-receiver string
@@ -345,10 +259,10 @@ export function lowerArrayMethodCall(lowerer, call, access) {
345
259
  const deleteCount = call.arguments.length === 0
346
260
  ? { kind: "numLit", value: 0, type: F64, loc }
347
261
  : call.arguments[1]
348
- ? lowerOptionalDefaultArg(lowerer, call.arguments[1], F64, deleteCountDefault)
262
+ ? lowerOptionalArgument(lowerer, call.arguments[1], F64, deleteCountDefault)
349
263
  : { kind: "numLit", value: Infinity, type: F64, loc };
350
264
  const itemNodes = call.arguments.slice(2);
351
- const itemProbes = itemNodes.map((arg) => probeLower(lowerer, arg));
265
+ const itemProbes = itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
352
266
  const statefulItems = itemProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
353
267
  const items = statefulItems
354
268
  ? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
@@ -431,7 +345,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
431
345
  loc,
432
346
  };
433
347
  }
434
- const valueProbes = call.arguments.map((arg) => probeLower(lowerer, arg));
348
+ const valueProbes = call.arguments.map((arg) => tryLowerExpression(lowerer, arg));
435
349
  if (valueProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null)) {
436
350
  const items = lowerArrayValueItems(lowerer, call.arguments.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc);
437
351
  return {
@@ -494,8 +408,12 @@ export function lowerArrayMethodCall(lowerer, call, access) {
494
408
  lowerer.unsupported("SC1090", argNode, "spread arguments to concat (pass the array itself — concat already spreads array arguments)");
495
409
  }
496
410
  const argIr = lowerer.mapTypeOf(lowerer.typeOf(argNode));
497
- if (argIr !== null && argIr.kind === "array" && typeEquals(argIr.elem, elem)) {
498
- if (elem.kind === "array" && typeEquals(argIr, elem)) {
411
+ const probedArg = probedUntyped ? tryLowerExpression(lowerer, argNode) : null;
412
+ const argArrayType = probedArg?.type.kind === "array"
413
+ ? probedArg.type
414
+ : argIr?.kind === "array" ? argIr : null;
415
+ if (argArrayType !== null && typeEquals(argArrayType.elem, elem)) {
416
+ if (elem.kind === "array" && typeEquals(argArrayType, elem)) {
499
417
  // number[][].concat(inner: number[]) — TS says element, JS's
500
418
  // IsArray says spread; no honest static answer exists.
501
419
  lowerer.unsupported("SC1090", argNode, "concat of a bare inner array onto an array-of-arrays (JS would SPREAD it one level — wrap it: a.concat([inner]))");
@@ -596,7 +514,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
596
514
  }
597
515
  if (name === "find" || name === "findIndex" || name === "findLast" ||
598
516
  name === "findLastIndex" || name === "some" || name === "every") {
599
- return lowerArrayFindLikeCall(lowerer, call, access, name, elem);
517
+ return lowerArrayFindLikeCall(lowerer, call, access, name, elem, probedUntyped);
600
518
  }
601
519
  if (name === "at")
602
520
  return lowerArrayAtCall(lowerer, call, access, elem);
@@ -623,7 +541,7 @@ function lowerArraySearchCall(lowerer, call, access, method, elem) {
623
541
  }
624
542
  // Preserve the existing fast path for an ordinary, non-union needle. Its
625
543
  // runtime implementation already has exact primitive/reference equality.
626
- if (elem.kind !== "union" && typeEquals(needle.type, elem)) {
544
+ if (elem.kind !== "union" && elem.kind !== "bigint" && typeEquals(needle.type, elem)) {
627
545
  return {
628
546
  kind: "arrIntrinsic",
629
547
  method,
@@ -823,7 +741,8 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
823
741
  * loop passes exactly what it declares (JS passes everything; a callback
824
742
  * only sees the parameters it names). Returns the lowered callback and
825
743
  * its declared arity. */
826
- function hofCallbackArg(lowerer, argNode, lead, arrT) {
744
+ function hofCallbackArg(lowerer, argNode, lead, arrT, bindUntyped = false) {
745
+ const full = [...lead, F64, arrT];
827
746
  // A DYN-receiver HOF's callback (`parsed.flatMap((value) => ...)`):
828
747
  // the contextual signature types the unannotated param `any` (the
829
748
  // receiver is checker-`any[]`), while the VALUE each call receives is
@@ -857,6 +776,43 @@ function hofCallbackArg(lowerer, argNode, lead, arrT) {
857
776
  lowerer.jsvalParamOverrides.add(p);
858
777
  });
859
778
  }
779
+ // A checker-untyped array expression whose lowered value supplies a
780
+ // concrete static element type still contextually types an inline HOF
781
+ // callback with any/unknown. Bind only unannotated parameters to the
782
+ // exact ABI the synthesized loop passes. The override is temporary:
783
+ // one callback AST may be lowered under several generic instances.
784
+ const contextual = [];
785
+ const previousImplicit = lowerer.implicitParamTypes;
786
+ let contextualTs = null;
787
+ if (bindUntyped && (ts.isArrowFunction(argNode) || ts.isFunctionExpression(argNode))) {
788
+ argNode.parameters.forEach((param, i) => {
789
+ const expected = full[i];
790
+ if (expected === undefined || expected.kind === "dyn" || expected.kind === "jsval" ||
791
+ !ts.isIdentifier(param.name) || param.type || param.initializer || param.dotDotDotToken) {
792
+ return;
793
+ }
794
+ const checkerType = lowerer.checker.getTypeAtLocation(param.name);
795
+ if ((checkerType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0)
796
+ return;
797
+ const symbol = lowerer.checker.getSymbolAtLocation(param.name);
798
+ if (!symbol)
799
+ return;
800
+ contextual.push({ symbol, previous: lowerer.runtimeOptionalBindingTypes.get(symbol) });
801
+ lowerer.runtimeOptionalBindingTypes.set(symbol, expected);
802
+ const bodyExpected = expected.kind === "union" && lowerer.armTag(expected.unionId, UNDEFINED_T) >= 0
803
+ ? lowerer.stripUndefinedArm(expected)
804
+ : expected;
805
+ const expectedTs = bodyExpected.kind === "string" ? lowerer.checker.getStringType() :
806
+ bodyExpected.kind === "f64" ? lowerer.checker.getNumberType() :
807
+ bodyExpected.kind === "bool" ? lowerer.checker.getBooleanType() : null;
808
+ if (expectedTs !== null) {
809
+ contextualTs ??= new Map(previousImplicit ?? []);
810
+ contextualTs.set(symbol, expectedTs);
811
+ }
812
+ });
813
+ }
814
+ if (contextualTs !== null)
815
+ lowerer.implicitParamTypes = contextualTs;
860
816
  let fnArg;
861
817
  try {
862
818
  fnArg = lowerer.lowerExpr(argNode);
@@ -864,8 +820,14 @@ function hofCallbackArg(lowerer, argNode, lead, arrT) {
864
820
  finally {
865
821
  for (const n of overridden)
866
822
  lowerer.chainNarrowedType.delete(n);
823
+ for (const { symbol, previous } of contextual) {
824
+ if (previous === undefined)
825
+ lowerer.runtimeOptionalBindingTypes.delete(symbol);
826
+ else
827
+ lowerer.runtimeOptionalBindingTypes.set(symbol, previous);
828
+ }
829
+ lowerer.implicitParamTypes = previousImplicit;
867
830
  }
868
- const full = [...lead, F64, arrT];
869
831
  if (fnArg.type.kind !== "func" ||
870
832
  fnArg.type.params.length > full.length ||
871
833
  !fnArg.type.params.every((p, i) => typeEquals(p, full[i]))) {
@@ -1024,8 +986,9 @@ function truthyFilterCallback(lowerer, valueT, loc) {
1024
986
  }
1025
987
  /** The predicate result kinds `.filter()` accepts. JS applies ToBoolean to
1026
988
  * whatever the callback answers, so a bool is not required: the scalars and
1027
- * the reference kinds have constant or by-value answers, and a union is fine
1028
- * when every arm does. void/dyn/jsval/caught stay out — see below. */
989
+ * the reference kinds have constant or by-value answers, checked-dynamic
990
+ * values ask their runtime kind, and a union is fine when every arm does.
991
+ * void/jsval/caught stay out — see below. */
1029
992
  function filterPredicateOk(lowerer, ret) {
1030
993
  if (ret.kind === "bool")
1031
994
  return true;
@@ -1035,10 +998,13 @@ function filterPredicateOk(lowerer, ret) {
1035
998
  // compiled ABI has already discarded it. Treating void as constantly
1036
999
  // falsy would silently answer [] where Node answers [1]. Fenced until
1037
1000
  // the returned value can be preserved through the void ABI.
1038
- // dyn/jsval/caught stay out too: no native ToBoolean to compile against.
1001
+ // A checked-dynamic value retains its runtime kind, so scr_dyn_truthy can
1002
+ // apply JavaScript ToBoolean exactly. Island and caught values stay out.
1039
1003
  if (ret.kind === "void")
1040
1004
  return false;
1041
- if (ret.kind === "dyn" || ret.kind === "jsval" || ret.kind === "caught")
1005
+ if (ret.kind === "dyn")
1006
+ return true;
1007
+ if (ret.kind === "jsval" || ret.kind === "caught")
1042
1008
  return false;
1043
1009
  if (ret.kind === "union") {
1044
1010
  const def = lowerer.unions.get(ret.unionId);
@@ -1058,9 +1024,9 @@ function filterCond(call, fnRet, loc) {
1058
1024
  // `void` returns to void and a standalone `null` return to the unit-ONLY
1059
1025
  // UNION, which routes through toBool below; ir/validate.ts rejects a
1060
1026
  // bare unit return type outright). filterPredicateOk already rejected
1061
- // the arms with no native ToBoolean (dyn/caught) — which is exactly what
1062
- // requireTruthyUnion checks — and it did so at the call site, where a
1063
- // real node exists for the diagnostic.
1027
+ // unsafe union arms (dyn/caught) at the call site, where a real node
1028
+ // exists for the diagnostic. A bare dyn value is handled directly by
1029
+ // the runtime's kind-aware ToBoolean.
1064
1030
  return { kind: "toBool", operand: call, type: BOOL, loc };
1065
1031
  }
1066
1032
  /** Interned synthetic loop function for one (method, elem, fnRet, arity)
@@ -1113,7 +1079,7 @@ export function lowerTupleReadMethodCall(lowerer, call, access) {
1113
1079
  receiver ??= lowerer.lowerExpr(access.expression);
1114
1080
  if (receiver.type.kind !== "record")
1115
1081
  return null;
1116
- if (!pureReemittable(receiver)) {
1082
+ if (!isSafeToRepeat(receiver)) {
1117
1083
  lowerer.noLowering(`${lowerer.checker.typeToString(lowerer.typeOf(access.expression))}.${name}`, call, "tuple receivers snapshot per position, so only effect-free receivers lower — bind the tuple to a const first");
1118
1084
  }
1119
1085
  const loc = locOf(call);
@@ -1400,14 +1366,14 @@ function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc, outElem
1400
1366
  * backwards (`i = n - 1; i >= 0; i--`), exactly the es2023 spec's
1401
1367
  * descending index walk. All require a bool-returning callback
1402
1368
  * (JS's ToBoolean of arbitrary predicate results has no lowering). */
1403
- function lowerArrayFindLikeCall(lowerer, call, access, method, elem) {
1369
+ function lowerArrayFindLikeCall(lowerer, call, access, method, elem, bindUntyped = false) {
1404
1370
  const loc = locOf(call);
1405
1371
  const receiver = lowerer.lowerExpr(access.expression);
1406
1372
  const arrT = arrayOf(elem);
1407
1373
  const argNode = call.arguments[0];
1408
1374
  if (!argNode)
1409
1375
  lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
1410
- const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [arrayValueType(lowerer, elem)], arrT);
1376
+ const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [arrayValueType(lowerer, elem)], arrT, bindUntyped);
1411
1377
  // JS takes the ToBoolean of the predicate's result — allowed wherever
1412
1378
  // that ToBoolean has a static answer (bool passes through; f64/string
1413
1379
  // by value; a truthy-answerable union by its arm — the
@@ -1437,7 +1403,7 @@ function lowerArrayFindLikeCall(lowerer, call, access, method, elem) {
1437
1403
  // found element passes through untouched; otherwise it wraps into its
1438
1404
  // arm. A union element whose result union differs would need the
1439
1405
  // union-into-union re-tag that doesn't exist — fenced.
1440
- const resultT = lowerer.irTypeOf(call);
1406
+ const resultT = bindUntyped ? arrayValueType(lowerer, elem) : lowerer.irTypeOf(call);
1441
1407
  if (resultT.kind !== "union")
1442
1408
  lowerer.badType(call, lowerer.typeOf(call)); // defensive: T | undefined always maps to a union
1443
1409
  const undefTag = lowerer.armTag(resultT.unionId, UNDEFINED_T);
@@ -1605,7 +1571,7 @@ function someEveryHelper(lowerer, method, elem, fnRet, arity, loc) {
1605
1571
  countedFor(loc, varRef("n.0", F64, loc), () => [
1606
1572
  {
1607
1573
  kind: "if",
1608
- cond: getHasExpr(arrT, loc),
1574
+ cond: currentArrayIndexPresent(arrT, loc),
1609
1575
  then: [{
1610
1576
  kind: "if",
1611
1577
  cond: method === "some" ? callF : { kind: "unary", op: "!", operand: callF, type: BOOL, loc },
@@ -1703,7 +1669,7 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
1703
1669
  body: [
1704
1670
  {
1705
1671
  kind: "if",
1706
- cond: arrayHasExpr(varRef("r.0", fnRet, loc), varRef("j.0", F64, loc), loc),
1672
+ cond: arrayIndexPresent(varRef("r.0", fnRet, loc), varRef("j.0", F64, loc), loc),
1707
1673
  then: [
1708
1674
  { kind: "varDecl", localId: "inner.0", init: arrayValueRead(lowerer, varRef("r.0", fnRet, loc), varRef("j.0", F64, loc), inner, loc), loc },
1709
1675
  arrayValueStore(lowerer, varRef("out.0", fnRet, loc), { kind: "arrIntrinsic", method: "length", receiver: varRef("out.0", fnRet, loc), args: [], type: F64, loc }, varRef("inner.0", arrayValueType(lowerer, inner), loc), inner, loc),
@@ -1720,7 +1686,7 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
1720
1686
  countedFor(loc, varRef("n.0", F64, loc), () => [
1721
1687
  {
1722
1688
  kind: "if",
1723
- cond: getHasExpr(arrT, loc),
1689
+ cond: currentArrayIndexPresent(arrT, loc),
1724
1690
  then: [
1725
1691
  {
1726
1692
  kind: "varDecl",
@@ -1843,7 +1809,7 @@ function reduceHelper(lowerer, method, elem, accT, fnRet, arity, hasInit, loc) {
1843
1809
  ...(hasInit
1844
1810
  ? [{ kind: "varDecl", localId: "acc.0", init: varRef("z.0", accT, loc), loc }]
1845
1811
  : [
1846
- { kind: "while", cond: { kind: "ternary", cond: inRange, then: { kind: "unary", op: "!", operand: getHasExpr(arrT, loc), type: BOOL, loc }, else_: boolLit(false, loc), type: BOOL, loc }, body: [advance], loc },
1812
+ { kind: "while", cond: { kind: "ternary", cond: inRange, then: { kind: "unary", op: "!", operand: currentArrayIndexPresent(arrT, loc), type: BOOL, loc }, else_: boolLit(false, loc), type: BOOL, loc }, body: [advance], loc },
1847
1813
  { kind: "if", cond: { kind: "unary", op: "!", operand: inRange, type: BOOL, loc }, then: [{ kind: "throw", value: { kind: "libCall", fn: "error.new", args: [{ kind: "strLit", value: "Reduce of empty array with no initial value", type: STRING, loc }], type: { kind: "object", className: "%TypeError" }, loc }, loc }], else_: null, loc },
1848
1814
  { kind: "varDecl", localId: "acc.0", init: lowerer.coerceToExpected(at(i), accT), loc },
1849
1815
  advance,
@@ -1861,7 +1827,7 @@ function reduceHelper(lowerer, method, elem, accT, fnRet, arity, hasInit, loc) {
1861
1827
  value: { kind: "bin", op: right ? "-" : "+", left: i, right: numLit(1, loc), type: F64, loc },
1862
1828
  loc,
1863
1829
  },
1864
- body: [{ kind: "if", cond: getHasExpr(arrT, loc), then: [
1830
+ body: [{ kind: "if", cond: currentArrayIndexPresent(arrT, loc), then: [
1865
1831
  {
1866
1832
  kind: "assign",
1867
1833
  localId: "acc.0",
@@ -1997,83 +1963,6 @@ function defaultStringCmpHelper(lowerer, loc) {
1997
1963
  });
1998
1964
  return name;
1999
1965
  }
2000
- /** Uint8Array.prototype.toSorted. The receiver/comparator expressions are
2001
- * evaluated before entering the helper; the helper snapshots with
2002
- * TypedArray.prototype.slice before its first comparison, then performs
2003
- * the same stable merge walk as Array.toSorted. Uint8Array's default
2004
- * comparator is numeric ascending, so its comparator-less form needs no
2005
- * string-conversion machinery. */
2006
- function lowerBytesToSortedCall(lowerer, call, access, bytesT) {
2007
- const loc = locOf(call);
2008
- if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
2009
- lowerer.noLowering(`.toSorted with ${call.arguments.length} arguments on Uint8Array`, call);
2010
- }
2011
- const receiver = lowerer.lowerExpr(access.expression);
2012
- const undefinedArg = call.arguments[0]
2013
- ? lowerStaticallyUndefinedArg(lowerer, call.arguments[0])
2014
- : null;
2015
- if (call.arguments.length === 0 || undefinedArg) {
2016
- const key = "bytes.toSorted:u8:default";
2017
- let helper = lowerer.arrHofHelpers.get(key);
2018
- if (!helper) {
2019
- helper = `%bytes.toSorted.${lowerer.arrHofHelpers.size}`;
2020
- lowerer.arrHofHelpers.set(key, helper);
2021
- lowerer.liftedFns.push(buildBytesSortFn(helper, 0, false, loc));
2022
- }
2023
- if (!undefinedArg || droppableStatic(undefinedArg)) {
2024
- return { kind: "call", callee: helper, args: [receiver], type: bytesT, loc };
2025
- }
2026
- // The default helper takes no comparator argument. Snapshot the
2027
- // receiver into a hidden local so the discarded undefined argument
2028
- // still evaluates after the receiver and before the helper call.
2029
- const saved = lowerer.declareHiddenLocal("%bytesSortRecv", bytesT);
2030
- const savedRef = {
2031
- kind: "varRef",
2032
- localId: saved.id,
2033
- type: bytesT,
2034
- loc,
2035
- };
2036
- return {
2037
- kind: "seqExpr",
2038
- stmts: [
2039
- { kind: "varDecl", localId: saved.id, init: receiver, loc },
2040
- { kind: "exprStmt", expr: undefinedArg, loc: undefinedArg.loc },
2041
- ],
2042
- result: {
2043
- kind: "call",
2044
- callee: helper,
2045
- args: [savedRef],
2046
- type: bytesT,
2047
- loc,
2048
- },
2049
- type: bytesT,
2050
- loc,
2051
- };
2052
- }
2053
- const argNode = call.arguments[0];
2054
- const fnArg = lowerer.lowerExpr(argNode);
2055
- if (fnArg.type.kind !== "func" ||
2056
- fnArg.type.params.length > 2 ||
2057
- !fnArg.type.params.every((p) => p.kind === "f64") ||
2058
- fnArg.type.ret.kind !== "f64") {
2059
- lowerer.badType(argNode, lowerer.typeOf(argNode));
2060
- }
2061
- const arity = fnArg.type.params.length;
2062
- const key = `bytes.toSorted:u8:${arity}`;
2063
- let helper = lowerer.arrHofHelpers.get(key);
2064
- if (!helper) {
2065
- helper = `%bytes.toSorted.${lowerer.arrHofHelpers.size}`;
2066
- lowerer.arrHofHelpers.set(key, helper);
2067
- lowerer.liftedFns.push(buildBytesSortFn(helper, arity, true, loc));
2068
- }
2069
- return {
2070
- kind: "call",
2071
- callee: helper,
2072
- args: [receiver, fnArg],
2073
- type: bytesT,
2074
- loc,
2075
- };
2076
- }
2077
1966
  /** `n = a.length` — the once-up-front length read every array HOF loop
2078
1967
  * starts with (locals a.0/n.0 by convention). */
2079
1968
  function readLenStmt(arrT, loc) {
@@ -2457,7 +2346,15 @@ export function lowerMapMethodCall(lowerer, call, access) {
2457
2346
  const name = access.name.text;
2458
2347
  if (!MAP_METHODS.has(name) && !MAP_ITER_METHODS.has(name))
2459
2348
  return null;
2460
- const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
2349
+ let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
2350
+ let probedUntyped = false;
2351
+ if (receiverIr?.kind !== "map" && isJsSourceFile(access.getSourceFile())) {
2352
+ const probed = tryLowerExpression(lowerer, access.expression);
2353
+ if (probed?.type.kind === "map") {
2354
+ receiverIr = probed.type;
2355
+ probedUntyped = true;
2356
+ }
2357
+ }
2461
2358
  if (receiverIr?.kind !== "map")
2462
2359
  return null;
2463
2360
  if (!lowerer.isStdlibMember(access))
@@ -2501,7 +2398,7 @@ export function lowerMapMethodCall(lowerer, call, access) {
2501
2398
  return lowerMapIterDrainCall(lowerer, call, receiver, receiverIr, name);
2502
2399
  }
2503
2400
  // forEach
2504
- return lowerer.lowerMapForEachCall(call, receiver, receiverIr);
2401
+ return lowerMapForEachCall(lowerer, call, receiver, receiverIr, probedUntyped);
2505
2402
  }
2506
2403
  /** The iterator methods the lowering DOES cover — in exactly one context. */
2507
2404
  const MAP_ITER_METHODS = new Set(["keys", "values", "entries"]);
@@ -2621,12 +2518,52 @@ function buildMapIterDrainFn(name, mapT, method, elemT, tupleT, loc) {
2621
2518
  * callback throws. That is JS's live-iteration contract, Node-verified
2622
2519
  * (SEMANTICS.md). The callback receives (value, key) like JS; declaring
2623
2520
  * fewer parameters — (value) or () — is ordinary TS and supported. */
2624
- export function lowerMapForEachCall(lowerer, call, receiver, mapT) {
2521
+ export function lowerMapForEachCall(lowerer, call, receiver, mapT, bindUntyped = false) {
2625
2522
  const loc = locOf(call);
2626
2523
  const argNode = call.arguments[0];
2627
2524
  if (!argNode)
2628
2525
  lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
2629
- const fnArg = lowerer.lowerExpr(argNode);
2526
+ const previousRuntime = [];
2527
+ const previousImplicit = lowerer.implicitParamTypes;
2528
+ let contextualTs = null;
2529
+ if (bindUntyped && (ts.isArrowFunction(argNode) || ts.isFunctionExpression(argNode))) {
2530
+ const expected = [mapT.value, mapT.key];
2531
+ argNode.parameters.slice(0, 2).forEach((param, i) => {
2532
+ const type = expected[i];
2533
+ if (type === undefined || !ts.isIdentifier(param.name) || param.type || param.initializer || param.dotDotDotToken)
2534
+ return;
2535
+ const checkerType = lowerer.checker.getTypeAtLocation(param.name);
2536
+ if ((checkerType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0)
2537
+ return;
2538
+ const symbol = lowerer.checker.getSymbolAtLocation(param.name);
2539
+ if (!symbol)
2540
+ return;
2541
+ previousRuntime.push({ symbol, type: lowerer.runtimeOptionalBindingTypes.get(symbol) });
2542
+ lowerer.runtimeOptionalBindingTypes.set(symbol, type);
2543
+ const primitive = type.kind === "string" ? lowerer.checker.getStringType() :
2544
+ type.kind === "f64" ? lowerer.checker.getNumberType() :
2545
+ type.kind === "bool" ? lowerer.checker.getBooleanType() : null;
2546
+ if (primitive !== null) {
2547
+ contextualTs ??= new Map(previousImplicit ?? []);
2548
+ contextualTs.set(symbol, primitive);
2549
+ }
2550
+ });
2551
+ }
2552
+ if (contextualTs !== null)
2553
+ lowerer.implicitParamTypes = contextualTs;
2554
+ let fnArg;
2555
+ try {
2556
+ fnArg = lowerer.lowerExpr(argNode);
2557
+ }
2558
+ finally {
2559
+ lowerer.implicitParamTypes = previousImplicit;
2560
+ for (const { symbol, type } of previousRuntime) {
2561
+ if (type === undefined)
2562
+ lowerer.runtimeOptionalBindingTypes.delete(symbol);
2563
+ else
2564
+ lowerer.runtimeOptionalBindingTypes.set(symbol, type);
2565
+ }
2566
+ }
2630
2567
  if (fnArg.type.kind !== "func" ||
2631
2568
  fnArg.type.params.length > 2 ||
2632
2569
  (fnArg.type.params.length >= 1 && !typeEquals(fnArg.type.params[0], mapT.value)) ||
@@ -2749,8 +2686,8 @@ export function lowerSetMethodCall(lowerer, call, access) {
2749
2686
  // Set<union-of-signatures>, but the VALUE lowered as a real Set of
2750
2687
  // identity tokens (the new-Set probe) — the lowered receiver's type
2751
2688
  // is the honest dispatch key.
2752
- if (receiverIr === null && isJsSourceFile(access.getSourceFile())) {
2753
- const probed = probeLower(lowerer, access.expression);
2689
+ if (receiverIr?.kind !== "set" && isJsSourceFile(access.getSourceFile())) {
2690
+ const probed = tryLowerExpression(lowerer, access.expression);
2754
2691
  if (probed?.type.kind === "set")
2755
2692
  receiverIr = probed.type;
2756
2693
  }
@@ -4401,1271 +4338,6 @@ function buildMapSeedFn(mapT, name, arrT, tupleT, kT, vT, loc) {
4401
4338
  loc,
4402
4339
  };
4403
4340
  }
4404
- /** Regex method calls, both directions: `re.test(s)` on a regex receiver,
4405
- * and `s.replace(re, tpl)` / `s.replaceAll(re, tpl)` / `s.split(re)` on a
4406
- * string receiver whose FIRST ARGUMENT is a regex (the string-pattern
4407
- * overloads keep their island lowering — the argument's mapped type is
4408
- * what routes here, before lowerIslandMethodCall can claim the name).
4409
- * Null when neither shape matches. */
4410
- export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
4411
- // chainBlocked, not a raw token test: an optional-chain re-dispatch
4412
- // (`rawName?.match(re)` — the receiver reads back chain-narrowed)
4413
- // rides the same lowering as the plain spelling.
4414
- if (lowerer.chainBlocked(access, call))
4415
- return null;
4416
- // A validated dyn receiver (lowerStringMethodCall's story) is a STRING
4417
- // by construction — the symbol gate doesn't apply to `any` receivers.
4418
- if (dynReceiver === undefined && !lowerer.isStdlibMember(access))
4419
- return null;
4420
- const name = access.name.text;
4421
- const receiverKind = dynReceiver ? "string" : lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind;
4422
- const lowerReceiver = () => {
4423
- if (dynReceiver)
4424
- return dynReceiver();
4425
- if (receiverKind === "string")
4426
- return lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
4427
- if (receiverKind === "regex")
4428
- return lowerMethodReceiver(lowerer, access.expression, { kind: "regex" }, access.name.text);
4429
- return lowerer.lowerExpr(access.expression);
4430
- };
4431
- const loc = locOf(call);
4432
- if (receiverKind === "regex" && name === "test") {
4433
- // The statefulness fence, at compile time where the flags are
4434
- // visible: a literal receiver (possibly parenthesized). Values that
4435
- // flow through variables hit the same fence at runtime.
4436
- let recv = access.expression;
4437
- while (ts.isParenthesizedExpression(recv))
4438
- recv = recv.expression;
4439
- if (ts.isRegularExpressionLiteral(recv)) {
4440
- const flags = recv.text.slice(recv.text.lastIndexOf("/") + 1);
4441
- if (flags.includes("g") || flags.includes("y")) {
4442
- lowerer.unsupported("SC1121", call);
4443
- }
4444
- }
4445
- const receiver = lowerReceiver();
4446
- const args = call.arguments.map((a) => lowerer.lowerExpr(a));
4447
- return { kind: "regexIntrinsic", method: "test", receiver, args, type: BOOL, loc };
4448
- }
4449
- // `re.exec(s)` for non-g/y regexes: spec-identical to `s.match(re)`
4450
- // (Symbol.match delegates to exec when lastIndex is out of play), so
4451
- // it lowers to the SAME match intrinsic with the operands swapped —
4452
- // the honest `string[] | null` slice, nonparticipating captures ""
4453
- // (match's documented rule). The g/y statefulness fence applies at
4454
- // compile time on literal receivers, exactly test()'s stance; values
4455
- // reaching the runtime with those flags abort there.
4456
- if (receiverKind === "regex" && name === "exec") {
4457
- if (call.arguments.length !== 1)
4458
- return null; // exec takes exactly the subject
4459
- let recv = access.expression;
4460
- while (ts.isParenthesizedExpression(recv))
4461
- recv = recv.expression;
4462
- if (ts.isRegularExpressionLiteral(recv)) {
4463
- const flags = recv.text.slice(recv.text.lastIndexOf("/") + 1);
4464
- if (flags.includes("g") || flags.includes("y")) {
4465
- lowerer.unsupported("SC1121", call);
4466
- }
4467
- }
4468
- const re = lowerReceiver();
4469
- const subject = lowerer.lowerExprExpecting(call.arguments[0], STRING);
4470
- const resultT = { kind: "union", unionId: lowerer.unions.intern([arrayOf(STRING), { kind: "nullT" }]) };
4471
- return { kind: "regexIntrinsic", method: "match", receiver: subject, args: [re], type: resultT, loc };
4472
- }
4473
- // `s.match(re)` for non-g/y regexes: Node's exec-shaped result reduced
4474
- // to the honest slice — the `string[] | null` union holding
4475
- // [whole match, ...captures] or the null arm. The g-flag match returns
4476
- // EVERY match (a different shape) and /y is stateful — both fence at
4477
- // compile time on literal arguments (values reaching the runtime with
4478
- // those flags abort, the test() stance). `.index`/`.input` reads on
4479
- // the result fence per member (array-typed value); `.groups` reads
4480
- // desugar at their access sites when the regex is statically known
4481
- // (lowerMatchGroupsRead).
4482
- // `s.match(re)` also claims a NULLABLE string receiver (string + unit
4483
- // arms — `process.versions.openssl.match(...)`, the Dict<string>
4484
- // member the node suite's crypto helper reads): the checked
4485
- // extraction narrows to the string arm and a unit value throws the
4486
- // catchable TypeError at the read, where Node's own member read
4487
- // throws — tsc only admits the spelling in JS sources.
4488
- const nullableStringRecv = receiverKind === "union" &&
4489
- name === "match" &&
4490
- (() => {
4491
- const t = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
4492
- if (t?.kind !== "union")
4493
- return false;
4494
- const arms = lowerer.unions.get(t.unionId)?.arms ?? [];
4495
- return arms.some((a) => a.kind === "string") && arms.every((a) => a.kind === "string" || isUnitType(a));
4496
- })();
4497
- if ((receiverKind === "string" || nullableStringRecv) && name === "match") {
4498
- const arg0 = call.arguments[0];
4499
- if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
4500
- return null; // string-pattern match: the SC2020 fence
4501
- if (call.arguments.length !== 1)
4502
- return null;
4503
- let reNode = arg0;
4504
- while (ts.isParenthesizedExpression(reNode))
4505
- reNode = reNode.expression;
4506
- if (ts.isRegularExpressionLiteral(reNode)) {
4507
- const flags = reNode.text.slice(reNode.text.lastIndexOf("/") + 1);
4508
- if (flags.includes("g") || flags.includes("y")) {
4509
- lowerer.unsupported("SC1120", call, "'.match()' with the 'g' or 'y' flag (an every-match array is a different shape — use replaceAll/split, or test() per position)");
4510
- }
4511
- }
4512
- const receiver = nullableStringRecv
4513
- ? lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text)
4514
- : lowerReceiver();
4515
- const re = lowerer.lowerExpr(arg0);
4516
- // RegExpMatchArray | null maps to the string[] | null union by
4517
- // itself; intern it directly when the checker's spelling doesn't —
4518
- // or when it maps to something WIDER (an optional-chain call node
4519
- // types `s?.match(re)` with the chain's `| undefined`; the intrinsic
4520
- // itself answers string[] | null, and the chain wrapper widens).
4521
- const exactT = { kind: "union", unionId: lowerer.unions.intern([arrayOf(STRING), { kind: "nullT" }]) };
4522
- const mapped = lowerer.mapTypeOf(lowerer.typeOf(call));
4523
- const resultT = mapped && typeEquals(mapped, exactT) ? mapped : exactT;
4524
- return { kind: "regexIntrinsic", method: "match", receiver, args: [re], type: resultT, loc };
4525
- }
4526
- // `s.matchAll(re)` — the every-match iterator drained EAGERLY into a
4527
- // string[][] (one honest match slice per row — match's rule). Lazy vs
4528
- // eager is unobservable here: strings are immutable, and the spec
4529
- // clones the regex at the call, so nothing can perturb the drain. The
4530
- // eager array IS what the two lowered consumers see anyway (the
4531
- // immediate [...spread] and the for-of walk); a stored iterator's
4532
- // .next() fences as an array member like any other. Non-global
4533
- // regexes throw Node's exact TypeError at runtime, catchably
4534
- // (replaceAll's stance).
4535
- if (receiverKind === "string" && name === "matchAll") {
4536
- const arg0 = call.arguments[0];
4537
- if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
4538
- return null; // string-pattern form: the SC2020 fence
4539
- if (call.arguments.length !== 1)
4540
- return null;
4541
- const receiver = lowerReceiver();
4542
- const re = lowerer.lowerExpr(arg0);
4543
- return {
4544
- kind: "regexIntrinsic",
4545
- method: "matchAll",
4546
- receiver,
4547
- args: [re],
4548
- type: arrayOf(arrayOf(STRING)),
4549
- loc,
4550
- };
4551
- }
4552
- // `s.search(re)` — the first match's UTF-16 index, or -1. No g/y fence
4553
- // (unlike test/match): Symbol.search neither reads nor writes lastIndex
4554
- // — a fresh exec from position 0, so /g is irrelevant and /y anchors at
4555
- // 0, exactly Node. Never throws.
4556
- if (receiverKind === "string" && name === "search") {
4557
- const arg0 = call.arguments[0];
4558
- if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
4559
- return null; // string-pattern form: the SC2020 fence
4560
- if (call.arguments.length !== 1)
4561
- return null;
4562
- const receiver = lowerReceiver();
4563
- const re = lowerer.lowerExpr(arg0);
4564
- return { kind: "regexIntrinsic", method: "search", receiver, args: [re], type: F64, loc };
4565
- }
4566
- if (receiverKind === "string" &&
4567
- (name === "replace" || name === "replaceAll" || name === "split")) {
4568
- const arg0 = call.arguments[0];
4569
- if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
4570
- return null;
4571
- const receiver = lowerReceiver();
4572
- // Split's omitted or undefined limit is 2^32-1. Complete it here so
4573
- // both IR backends and the runtime have one required (regex, limit)
4574
- // shape; an optional-number value selects the default at runtime.
4575
- const args = name === "split"
4576
- ? [lowerer.lowerExpr(arg0), lowerSplitLimitArg(lowerer, call.arguments[1], loc)]
4577
- : call.arguments.map((a) => lowerer.lowerExpr(a));
4578
- if (name !== "split" && args[1]?.type.kind !== "string") {
4579
- lowerer.unsupported("SC1120", call.arguments[1] ?? call, "function replacement values (replacements must be string templates)");
4580
- }
4581
- return {
4582
- kind: "regexIntrinsic",
4583
- method: name,
4584
- receiver,
4585
- args,
4586
- type: name === "split" ? arrayOf(STRING) : STRING,
4587
- loc,
4588
- };
4589
- }
4590
- return null;
4591
- }
4592
- /** `s.slice(1, 4)` and friends → strIntrinsic. Null when this isn't an
4593
- * ambient string method call (caller keeps its generic rejection). Missing
4594
- * optional arguments are omitted from `args` — the backend fills the
4595
- * documented defaults; the IR never encodes them (Infinity isn't
4596
- * JSON-safe). tsc has already checked arity and argument types against
4597
- * ambient/scriptc.d.ts. */
4598
- export function lowerStringMethodCall(lowerer, call, access, dynReceiver) {
4599
- if (lowerer.chainBlocked(access, call))
4600
- return null;
4601
- if (dynReceiver === undefined && access.name.text === "localeCompare")
4602
- return lowerLocaleCompareCall(lowerer, call, access);
4603
- const entry = own(STR_METHODS, access.name.text);
4604
- if (!entry)
4605
- return null;
4606
- // A validated dyn receiver (`pkg.name.replace(...)` on a JSON.parse
4607
- // value) arrives pre-extracted through `dynReceiver`; its checker type
4608
- // is `any`, so the type/symbol gates don't apply — the dyn value's
4609
- // methods can only BE the string intrinsics.
4610
- if (dynReceiver === undefined) {
4611
- const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
4612
- // `require.main?.filename.startsWith(...)`: the checker types the
4613
- // receiver `string | undefined` (the chain's short-circuit arm), but
4614
- // the entry-module fold lowers it to a compile-time STRING — the
4615
- // suite harness's skip() shape. Everything else keeps the strict
4616
- // string gate.
4617
- if (receiverIr?.kind !== "string" && !isRequireMainFilename(lowerer, access.expression))
4618
- return null;
4619
- if (!lowerer.isStdlibMember(access))
4620
- return null;
4621
- }
4622
- // The lib declares optional parameters beyond the lowered forms
4623
- // (includes/startsWith/endsWith take a position); fence the unlowered
4624
- // arities instead of passing arguments the runtime doesn't take.
4625
- if (call.arguments.length < entry.minArgs || call.arguments.length > entry.maxArgs) {
4626
- lowerer.noLowering(`.${access.name.text} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"} on strings`, call);
4627
- }
4628
- const receiver = dynReceiver
4629
- ? dynReceiver()
4630
- : lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
4631
- const args = entry.method === "split"
4632
- ? [lowerer.lowerExpr(call.arguments[0]), lowerSplitLimitArg(lowerer, call.arguments[1], locOf(call))]
4633
- : call.arguments.map((a) => lowerer.lowerExpr(a));
4634
- // split's separator must BE a string here (a regex argument was
4635
- // claimed by lowerRegexMethodCall before this path) — the lib's
4636
- // `string | RegExp` union has no lowering as a VALUE.
4637
- if (entry.method === "split" && args[0].type.kind !== "string") {
4638
- lowerer.unsupported("SC1090", call.arguments[0], `'.split()' on a '${lowerer.fmt(args[0].type)}' separator (pass a string, or a regex literal)`);
4639
- }
4640
- // padStart/padEnd with the fill omitted: Node pads with " " — the
4641
- // same call with the default made explicit.
4642
- if ((entry.method === "padStart" || entry.method === "padEnd") && args.length === 1) {
4643
- args.push({ kind: "strLit", value: " ", type: STRING, loc: locOf(call) });
4644
- }
4645
- return {
4646
- kind: "strIntrinsic",
4647
- method: entry.method,
4648
- receiver,
4649
- args,
4650
- type: entry.result,
4651
- loc: locOf(call),
4652
- };
4653
- }
4654
- /** `a.localeCompare(b)` — the one-argument form only (locales/options
4655
- * select ICU collations that do not exist here). Lowers to an interned
4656
- * synthetic function returning -1/0/1 by CODE-UNIT order — the same
4657
- * ordering as the string relational operators — NOT Node's ICU default
4658
- * collation: a documented divergence (SEMANTICS.md; e.g. Node says
4659
- * "a" < "B" under ICU while code units say "B" < "a"). For same-case
4660
- * ASCII the orders agree. Null when the receiver isn't a stdlib string
4661
- * (caller keeps its generic rejection). */
4662
- function lowerLocaleCompareCall(lowerer, call, access) {
4663
- const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
4664
- if (receiverIr?.kind !== "string")
4665
- return null;
4666
- if (!lowerer.isStdlibMember(access))
4667
- return null;
4668
- const loc = locOf(call);
4669
- if (call.arguments.length !== 1) {
4670
- lowerer.noLowering(`.localeCompare with ${call.arguments.length} arguments`, call, "the locales/options parameters select ICU collations that have no lowering — " +
4671
- "pass exactly the comparison string");
4672
- }
4673
- const receiver = lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
4674
- const arg = lowerer.lowerExpr(call.arguments[0]);
4675
- if (arg.type.kind !== "string")
4676
- lowerer.badType(call.arguments[0], lowerer.typeOf(call.arguments[0]));
4677
- const key = "localeCompare";
4678
- let helper = lowerer.arrHofHelpers.get(key);
4679
- if (!helper) {
4680
- helper = `%str.localeCompare`;
4681
- lowerer.arrHofHelpers.set(key, helper);
4682
- lowerer.liftedFns.push(buildLocaleCompareFn(helper, loc));
4683
- }
4684
- return { kind: "call", callee: helper, args: [receiver, arg], type: F64, loc };
4685
- }
4686
- /** `return a < b ? -1 : a > b ? 1 : 0` over the strCmp primitive (the
4687
- * relational operators' exact machinery — one interned helper, no new IR
4688
- * or runtime surface). */
4689
- function buildLocaleCompareFn(name, loc) {
4690
- const cmp = (op) => ({ kind: "strCmp", op, left: varRef("a.0", STRING, loc), right: varRef("b.0", STRING, loc), type: BOOL, loc });
4691
- const body = [
4692
- {
4693
- kind: "return",
4694
- value: {
4695
- kind: "ternary",
4696
- cond: cmp("<"),
4697
- then: numLit(-1, loc),
4698
- else_: { kind: "ternary", cond: cmp(">"), then: numLit(1, loc), else_: numLit(0, loc), type: F64, loc },
4699
- type: F64,
4700
- loc,
4701
- },
4702
- loc,
4703
- },
4704
- ];
4705
- return {
4706
- name,
4707
- params: [
4708
- { localId: "a.0", name: "a", type: STRING },
4709
- { localId: "b.0", name: "b", type: STRING },
4710
- ],
4711
- returnType: F64,
4712
- locals: [
4713
- { id: "a.0", name: "a", type: STRING, mutable: true },
4714
- { id: "b.0", name: "b", type: STRING, mutable: true },
4715
- ],
4716
- body,
4717
- loc,
4718
- };
4719
- }
4720
- /* ── typed arrays / Buffer ─────────────────────────────────────────────── */
4721
- /** The typed-array constructors with a runtime representation, by lib
4722
- * interface name. The other TypedArray flavors (Int8Array, Float64Array,
4723
- * DataView, ...) fall through to the generic stdlib-constructor fence. */
4724
- const BYTES_CTORS = {
4725
- Uint8Array: "u8",
4726
- Uint32Array: "u32",
4727
- Int32Array: "i32",
4728
- Float32Array: "f32",
4729
- };
4730
- /** `new Uint8Array(...)` / `new Uint32Array(...)` / `new Float32Array(...)`
4731
- * (stdlib provenance — a user's own class with the name resolves through
4732
- * classBySymbol). Lowered argument shapes: none (empty), a length
4733
- * (zero-filled; ToIndex at runtime — invalid lengths throw Node's
4734
- * RangeError), a same-kind typed array or Buffer (an independent COPY —
4735
- * the readFile chain's `new Uint8Array(await readFile(p))`), a number[]
4736
- * literal (element-coerced; its contextual type is the lib's
4737
- * ArrayLike/Iterable union, which cannot map — the Set-seed pattern), or
4738
- * a number[]-typed value. ArrayBuffer/view forms are fenced: scriptc
4739
- * typed arrays own their storage. Null when this isn't a stdlib
4740
- * typed-array construction. */
4741
- export function lowerBytesNew(lowerer, expr, symbol) {
4742
- if (symbol && symbol.name === "DataView" && lowerer.isStdlibSymbol(symbol)) {
4743
- return lowerDataViewNew(lowerer, expr);
4744
- }
4745
- const elem = symbol ? own(BYTES_CTORS, symbol.name) : undefined;
4746
- if (!elem || !symbol || !lowerer.isStdlibSymbol(symbol))
4747
- return null;
4748
- const name = symbol.name;
4749
- const type = bytesOf(elem);
4750
- const loc = locOf(expr);
4751
- const args = expr.arguments ?? [];
4752
- if (args.length === 0)
4753
- return { kind: "bytesNew", source: null, type, loc };
4754
- if (args.length === 1 && !ts.isSpreadElement(args[0])) {
4755
- const argNode = args[0];
4756
- if (ts.isArrayLiteralExpression(argNode) && !argNode.elements.some(ts.isSpreadElement)) {
4757
- const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, F64));
4758
- const seed = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
4759
- return { kind: "bytesNew", source: seed, type, loc };
4760
- }
4761
- // The SYNTACTIC `new T(new ArrayBuffer(n))` and `new T(new
4762
- // SharedArrayBuffer(n))` forms — fresh-buffer construction (the
4763
- // shared spelling is the Atomics.wait sleep idiom's). The buffer
4764
- // never exists as a value: n must be a byte-length LITERAL divisible
4765
- // by the element size (tsc would admit any number; a bad one is
4766
- // Node's RangeError — rejected at compile time instead of
4767
- // half-lowering), and the whole expression is a zero-filled typed
4768
- // array of n/elemSize elements. Erasing the buffer is exact: nothing
4769
- // else can ever reference it, so neither sharing (scriptc has no
4770
- // threads) nor aliasing is observable — SEMANTICS.md documents the
4771
- // stance. The RESIZABLE form (a maxByteLength options bag) fences by
4772
- // name: resize needs the buffer to exist as a value, and none does.
4773
- if (ts.isNewExpression(argNode) &&
4774
- ts.isIdentifier(argNode.expression) &&
4775
- (argNode.expression.text === "ArrayBuffer" ||
4776
- argNode.expression.text === "SharedArrayBuffer") &&
4777
- lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(argNode.expression) ?? undefined)) {
4778
- const bufCtor = argNode.expression.text;
4779
- if ((argNode.arguments?.length ?? 0) > 1) {
4780
- lowerer.noLowering(`new ${name} over a resizable ${bufCtor}`, argNode, "a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer " +
4781
- "to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here " +
4782
- "erases into the view): drop the options bag");
4783
- }
4784
- const elemSize = elem === "u8" ? 1 : 4;
4785
- const lenArg = argNode.arguments?.length === 1 ? argNode.arguments[0] : undefined;
4786
- const lenT = lenArg ? lowerer.typeOf(lenArg) : null;
4787
- const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
4788
- if (byteLen === null || byteLen % elemSize !== 0 || byteLen < 0) {
4789
- lowerer.noLowering(`new ${name} over this ${bufCtor}`, argNode, `the byte length must be a number literal divisible by ${elemSize} — new ${name}(new ${bufCtor}(${elemSize}))`);
4790
- }
4791
- const count = { kind: "numLit", value: byteLen / elemSize, type: F64, loc };
4792
- return { kind: "bytesNew", source: count, type, loc };
4793
- }
4794
- const src = lowerer.lowerExpr(argNode);
4795
- if (src.type.kind === "union" && lowerer.armTag(src.type.unionId, UNDEFINED_T) >= 0) {
4796
- const present = lowerer.stripUndefinedArm(src.type);
4797
- if (present.kind === "array" && present.elem.kind === "f64") {
4798
- const undefTag = lowerer.armTag(src.type.unionId, UNDEFINED_T);
4799
- const presentTag = lowerer.armTag(src.type.unionId, present);
4800
- if (undefTag >= 0 && presentTag >= 0) {
4801
- const empty = { kind: "arrayLit", elems: [], type: present, loc };
4802
- const source = {
4803
- kind: "ternary",
4804
- cond: { kind: "unionIsTag", unionId: src.type.unionId, tag: undefTag, negated: false, value: src, type: BOOL, loc },
4805
- then: empty,
4806
- else_: { kind: "unionNarrow", unionId: src.type.unionId, tag: presentTag, value: src, type: present, loc },
4807
- type: present,
4808
- loc,
4809
- };
4810
- return { kind: "bytesNew", source, type, loc };
4811
- }
4812
- }
4813
- }
4814
- if (src.type.kind === "f64" ||
4815
- typeEquals(src.type, type) ||
4816
- (src.type.kind === "array" && src.type.elem.kind === "f64")) {
4817
- return { kind: "bytesNew", source: src, type, loc };
4818
- }
4819
- if (src.type.kind === "bytes") {
4820
- lowerer.noLowering(`new ${name} over a '${lowerer.fmt(src.type)}'`, argNode, "cross-kind typed-array conversion has no lowering — copy element by element");
4821
- }
4822
- lowerer.noLowering(`new ${name} over '${lowerer.fmt(src.type)}' values`, argNode, `supported: new ${name}(), (length), (typedArray) — always a copy — or (number[]); ` +
4823
- "ArrayBuffers and views do not exist here (narrow unions first)");
4824
- }
4825
- lowerer.noLowering(`new ${name} with ${args.length} arguments`, expr, `supported: new ${name}(), (length), (typedArray), or (number[])`);
4826
- }
4827
- /** `new DataView(x.buffer, byteOffset?, byteLength?)` (stdlib provenance).
4828
- * The first argument must be the SYNTACTIC `.buffer` of a typed-array/
4829
- * Buffer value — `x.buffer` names x's own storage (scriptc typed arrays
4830
- * always own it whole, byteOffset 0), so the view construction takes x
4831
- * itself as the receiver and no ArrayBuffer value ever exists.
4832
- * byteOffset/byteLength are optional f64s (omitted args OMITTED, like
4833
- * slice); bad indices THROW Node's RangeErrors catchably. The result is
4834
- * a true borrowed view: reads and writes through it alias x. */
4835
- function lowerDataViewNew(lowerer, expr) {
4836
- const loc = locOf(expr);
4837
- const args = expr.arguments ?? [];
4838
- const bufNode = args[0];
4839
- if (!bufNode || args.length > 3 || args.some(ts.isSpreadElement)) {
4840
- lowerer.noLowering(`new DataView with ${args.length} arguments`, expr, "supported: new DataView(x.buffer), (x.buffer, byteOffset), or (x.buffer, byteOffset, byteLength)");
4841
- }
4842
- // The FRESH-BUFFER form: `new DataView(new ArrayBuffer(n), ...)` — the
4843
- // buffer erases into the view (nothing else can ever reference it, so
4844
- // the aliasing a shared buffer would exhibit is unobservable): the
4845
- // view's storage is a fresh zero-filled n-byte allocation. n must be a
4846
- // non-negative integer LITERAL (tsc admits any number; a fractional
4847
- // one is ToIndex truncation nothing here implements). byteOffset and
4848
- // byteLength keep their runtime Node-RangeError story. The RESIZABLE
4849
- // form (a maxByteLength options bag) fences by name.
4850
- if (ts.isNewExpression(bufNode) &&
4851
- ts.isIdentifier(bufNode.expression) &&
4852
- bufNode.expression.text === "ArrayBuffer" &&
4853
- lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(bufNode.expression) ?? undefined)) {
4854
- if ((bufNode.arguments?.length ?? 0) > 1) {
4855
- lowerer.noLowering("new DataView over a resizable ArrayBuffer", bufNode, "a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer " +
4856
- "to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here " +
4857
- "erases into the view): drop the options bag");
4858
- }
4859
- const lenArg = bufNode.arguments?.length === 1 ? bufNode.arguments[0] : undefined;
4860
- const lenT = lenArg ? lowerer.typeOf(lenArg) : null;
4861
- const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
4862
- if (byteLen === null || !Number.isInteger(byteLen) || byteLen < 0) {
4863
- lowerer.noLowering("new DataView over this ArrayBuffer", bufNode, "the byte length must be a non-negative integer literal — new DataView(new ArrayBuffer(8))");
4864
- }
4865
- const count = { kind: "numLit", value: byteLen, type: F64, loc };
4866
- const receiver = { kind: "bytesNew", source: count, type: BYTES_U8, loc };
4867
- const idxArgs = args.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
4868
- return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
4869
- }
4870
- const hint = "views compile over a typed array's own storage — new DataView(x.buffer, byteOffset?, byteLength?) " +
4871
- "where x is a Uint8Array/Uint32Array/Float32Array/Buffer value — or a fresh buffer erased into " +
4872
- "the view: new DataView(new ArrayBuffer(n), ...); free-standing ArrayBuffers have no representation";
4873
- if (!ts.isPropertyAccessExpression(bufNode) || bufNode.name.text !== "buffer" || bufNode.questionDotToken) {
4874
- lowerer.noLowering("new DataView over this buffer expression", bufNode, hint);
4875
- }
4876
- const srcIr = lowerer.mapTypeOf(lowerer.typeOf(bufNode.expression));
4877
- if (srcIr?.kind !== "bytes" || !lowerer.isStdlibMember(bufNode)) {
4878
- lowerer.noLowering(`new DataView over '.buffer' of a '${lowerer.checker.typeToString(lowerer.typeOf(bufNode.expression))}'`, bufNode, hint);
4879
- }
4880
- const receiver = lowerer.lowerExpr(bufNode.expression);
4881
- const idxArgs = args.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
4882
- return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
4883
- }
4884
- /** Method calls on typed-array/Buffer receivers: slice and subarray (BOTH
4885
- * copy — subarray's sharing is the documented divergence), set(src,
4886
- * offset?), and the u8-only Buffer surface: toString(enc?) plus the
4887
- * whole numeric read/write family (fixed widths BE/LE and the
4888
- * variable-width read/writeUIntLE quartet — BUF_NUM_METHODS). Everything
4889
- * else the lib declares (fill, indexOf, reverse, ...) falls through to
4890
- * the SC2020 member fence. Null when this isn't a bytes method call. */
4891
- export function lowerBytesMethodCall(lowerer, call, access) {
4892
- if (lowerer.chainBlocked(access, call))
4893
- return null;
4894
- const name = access.name.text;
4895
- const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
4896
- if (receiverIr?.kind !== "bytes")
4897
- return null;
4898
- if (!lowerer.isStdlibMember(access))
4899
- return null;
4900
- const loc = locOf(call);
4901
- const nArgs = call.arguments.length;
4902
- if (receiverIr.elem === "u8" && name === "toSorted") {
4903
- return lowerBytesToSortedCall(lowerer, call, access, receiverIr);
4904
- }
4905
- if (receiverIr.elem === "u8" && name === "toReversed") {
4906
- if (nArgs !== 0) {
4907
- lowerer.noLowering(`.toReversed with ${nArgs} arguments on Uint8Array`, call);
4908
- }
4909
- return {
4910
- kind: "bytesIntrinsic",
4911
- method: "toReversed",
4912
- receiver: lowerer.lowerExpr(access.expression),
4913
- args: [],
4914
- type: receiverIr,
4915
- loc,
4916
- };
4917
- }
4918
- if (receiverIr.elem === "u8" && name === "with") {
4919
- if (nArgs !== 2 || call.arguments.some(ts.isSpreadElement)) {
4920
- lowerer.noLowering(`.with with ${nArgs} arguments on Uint8Array`, call);
4921
- }
4922
- return {
4923
- kind: "bytesIntrinsic",
4924
- method: "with",
4925
- receiver: lowerer.lowerExpr(access.expression),
4926
- args: [
4927
- lowerer.lowerExprExpecting(call.arguments[0], F64),
4928
- lowerer.lowerExprExpecting(call.arguments[1], F64),
4929
- ],
4930
- type: receiverIr,
4931
- loc,
4932
- };
4933
- }
4934
- if (receiverIr.elem === "u8" && name === "join") {
4935
- if (nArgs > 1 || call.arguments.some(ts.isSpreadElement)) {
4936
- lowerer.noLowering(`.join with ${nArgs} arguments on Uint8Array`, call);
4937
- }
4938
- const separatorDefault = {
4939
- kind: "strLit",
4940
- value: ",",
4941
- type: STRING,
4942
- loc,
4943
- };
4944
- const separator = call.arguments[0]
4945
- ? lowerOptionalDefaultArg(lowerer, call.arguments[0], STRING, separatorDefault)
4946
- : separatorDefault;
4947
- return {
4948
- kind: "bytesIntrinsic",
4949
- method: "join",
4950
- receiver: lowerer.lowerExpr(access.expression),
4951
- args: [separator],
4952
- type: STRING,
4953
- loc,
4954
- };
4955
- }
4956
- if (name === "slice" || name === "subarray") {
4957
- if (nArgs > 2) {
4958
- lowerer.noLowering(`.${name} with ${nArgs} arguments on typed arrays`, call);
4959
- }
4960
- const receiver = lowerer.lowerExpr(access.expression);
4961
- const args = call.arguments.map((a) => lowerer.lowerExprExpecting(a, F64));
4962
- // subarray is a VIEW (TypedArray.prototype.subarray aliases), and
4963
- // Buffer's slice() is subarray's deprecated Node alias — resolved by
4964
- // where the member is declared, the toString discipline below. Only
4965
- // the plain typed arrays' slice() copies (JS-exact).
4966
- const declaredOnBuffer = name === "slice" &&
4967
- (() => {
4968
- const nameSym = lowerer.checker.getSymbolAtLocation(access.name);
4969
- return nameSym !== undefined && lowerer.checker.declarationsOf(nameSym).some((d) => ts.isInterfaceDeclaration(d.parent) && d.parent.name.text === "Buffer");
4970
- })();
4971
- const method = name === "subarray" || declaredOnBuffer ? "subarray" : "slice";
4972
- return { kind: "bytesIntrinsic", method, receiver, args, type: receiverIr, loc };
4973
- }
4974
- if (name === "fill" && receiverIr.elem !== "u8") {
4975
- // TypedArray.prototype.fill on the non-u8 kinds: per-element value
4976
- // coercion, slice-clamped relative indices, never throws. u8
4977
- // receivers keep the Buffer fill family (string patterns, throwing
4978
- // offset validation) — lowerBufferInstanceMethod's surface.
4979
- if (nArgs < 1 || nArgs > 3) {
4980
- lowerer.noLowering(`.fill with ${nArgs} arguments on typed arrays`, call);
4981
- }
4982
- const receiver = lowerer.lowerExpr(access.expression);
4983
- const v = lowerer.lowerExprExpecting(call.arguments[0], F64);
4984
- const idx = call.arguments.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
4985
- return { kind: "bytesIntrinsic", method: "fillElem", receiver, args: [v, ...idx], type: receiverIr, loc };
4986
- }
4987
- if (name === "set") {
4988
- if (nArgs < 1 || nArgs > 2) {
4989
- lowerer.noLowering(`.set with ${nArgs} arguments on typed arrays`, call);
4990
- }
4991
- const receiver = lowerer.lowerExpr(access.expression);
4992
- const src = lowerer.lowerExpr(call.arguments[0]);
4993
- if (!typeEquals(src.type, receiverIr)) {
4994
- lowerer.noLowering(`.set from '${lowerer.fmt(src.type)}' values`, call.arguments[0], "only a same-kind typed array copies in (number[] sources have no lowering — narrow unions first)");
4995
- }
4996
- const args = [src];
4997
- if (nArgs === 2)
4998
- args.push(lowerer.lowerExprExpecting(call.arguments[1], F64));
4999
- return { kind: "bytesIntrinsic", method: "setFrom", receiver, args, type: VOID, loc };
5000
- }
5001
- if (name === "toString") {
5002
- // Buffer's toString(encoding?) — utf8 by default. A 0-arg toString
5003
- // resolved against the plain Uint8Array interface is JS's
5004
- // Array-toString (comma join), a different operation: fenced.
5005
- const declaredOnBuffer = (() => {
5006
- const nameSym = lowerer.checker.getSymbolAtLocation(access.name);
5007
- return nameSym !== undefined && lowerer.checker.declarationsOf(nameSym).some((d) => ts.isInterfaceDeclaration(d.parent) && d.parent.name.text === "Buffer");
5008
- })();
5009
- if (receiverIr.elem !== "u8" || !declaredOnBuffer) {
5010
- lowerer.noLowering("typed-array toString", call, 'Buffer.from(x).toString("utf8" | "hex" | "base64") is the lowered string conversion');
5011
- }
5012
- if (nArgs > 3)
5013
- lowerer.noLowering(`.toString with ${nArgs} arguments on Buffers`, call);
5014
- const receiver = lowerer.lowerExpr(access.expression);
5015
- const encNode = call.arguments[0];
5016
- let method = "toString";
5017
- let enc = { kind: "strLit", value: "utf8", type: STRING, loc };
5018
- if (encNode) {
5019
- const encType = lowerer.typeOf(encNode);
5020
- const encName = encType.isStringLiteralType()
5021
- ? knownBufEncoding(encType.value)
5022
- : undefined;
5023
- if (encName !== undefined) {
5024
- // Keep literals on the canonical, non-throwing fast path.
5025
- enc = { kind: "strLit", value: encName, type: STRING, loc };
5026
- }
5027
- else {
5028
- const undefinedArg = lowerStaticallyUndefinedArg(lowerer, encNode);
5029
- if (undefinedArg) {
5030
- // An explicit undefined is the omitted-encoding default. Keep
5031
- // the canonical, non-throwing path while preserving effects
5032
- // from equivalent spellings such as `void sideEffect()`.
5033
- enc = defaultAfterUndefined(undefinedArg, enc);
5034
- }
5035
- else {
5036
- // A BufferEncoding-typed variable selects its decoder at
5037
- // runtime. Optional variables default their undefined arm to
5038
- // utf8; the checked intrinsic canonicalizes every present
5039
- // alias/case and raises Node's ERR_UNKNOWN_ENCODING when a cast
5040
- // lets a bad value through.
5041
- enc = lowerOptionalDefaultArg(lowerer, encNode, STRING, enc);
5042
- method = "toStringVar";
5043
- }
5044
- }
5045
- }
5046
- // The range form toString(enc, start[, end]) decodes the clamped
5047
- // [start, end) byte window (Node's slice-then-decode). An omitted
5048
- // end stays omitted (2 intrinsic args) — the emitter supplies the
5049
- // receiver's length, because EXPLICIT negative ends clamp to empty
5050
- // in Node and no in-band sentinel can represent "omitted" safely.
5051
- if (nArgs > 1) {
5052
- const start = lowerer.lowerExprExpecting(call.arguments[1], F64);
5053
- if (nArgs === 3) {
5054
- const end = lowerer.lowerExprExpecting(call.arguments[2], F64);
5055
- return { kind: "bytesIntrinsic", method, receiver, args: [enc, start, end], type: STRING, loc };
5056
- }
5057
- return { kind: "bytesIntrinsic", method, receiver, args: [enc, start], type: STRING, loc };
5058
- }
5059
- return { kind: "bytesIntrinsic", method, receiver, args: [enc], type: STRING, loc };
5060
- }
5061
- // The Buffer-declared comparison/search/mutation surface. All of
5062
- // these resolve against the Buffer interface (the checker keeps most
5063
- // off plain typed arrays; where the lib DOES declare a same-named
5064
- // TypedArray member — indexOf, includes, fill — the semantics differ,
5065
- // so only Buffer-declared resolutions lower and u8 receivers gate the
5066
- // rest).
5067
- const declOnBuffer = (() => {
5068
- const nameSym = lowerer.checker.getSymbolAtLocation(access.name);
5069
- return nameSym !== undefined && lowerer.checker.declarationsOf(nameSym).some((d) => ts.isInterfaceDeclaration(d.parent) && d.parent.name.text === "Buffer");
5070
- })();
5071
- if (declOnBuffer && receiverIr.elem === "u8") {
5072
- const bufMethod = lowerBufferInstanceMethod(lowerer, call, access, name, loc);
5073
- if (bufMethod)
5074
- return bufMethod;
5075
- }
5076
- // The Buffer numeric read/write families — every fixed-width kind in
5077
- // both endiannesses ("Uint" and "UInt" alike: Node aliases both), plus
5078
- // the variable-width read/writeUIntLE quartet. The kind token rides as
5079
- // a strLit args[0]; omitted offsets complete to Node's default 0.
5080
- const numKind = own(BUF_NUM_METHODS, name);
5081
- if (numKind !== undefined) {
5082
- if (receiverIr.elem !== "u8") {
5083
- lowerer.noLowering(`.${name} on a '${lowerer.fmt(receiverIr)}'`, call, "the numeric families read/write Buffer bytes");
5084
- }
5085
- const write = name.startsWith("write");
5086
- const required = write ? 1 : 0; // value; offset defaults to 0
5087
- if (nArgs < required || nArgs > required + 1) {
5088
- lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
5089
- }
5090
- const receiver = lowerer.lowerExpr(access.expression);
5091
- const kind = { kind: "strLit", value: numKind, type: STRING, loc };
5092
- const args = [kind, ...call.arguments.map((a) => lowerer.lowerExprExpecting(a, F64))];
5093
- if (nArgs === required) {
5094
- args.push({ kind: "numLit", value: 0, type: F64, loc }); // Node's offset default
5095
- }
5096
- return { kind: "bytesIntrinsic", method: write ? "writeNum" : "readNum", receiver, args, type: F64, loc };
5097
- }
5098
- const varKind = own(BUF_NUM_VAR_METHODS, name);
5099
- if (varKind !== undefined) {
5100
- if (receiverIr.elem !== "u8") {
5101
- lowerer.noLowering(`.${name} on a '${lowerer.fmt(receiverIr)}'`, call, "the numeric families read/write Buffer bytes");
5102
- }
5103
- const write = name.startsWith("write");
5104
- const required = write ? 3 : 2; // Node declares offset AND byteLength required
5105
- if (nArgs !== required) {
5106
- lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
5107
- }
5108
- const receiver = lowerer.lowerExpr(access.expression);
5109
- const kind = { kind: "strLit", value: varKind, type: STRING, loc };
5110
- const args = [kind, ...call.arguments.map((a) => lowerer.lowerExprExpecting(a, F64))];
5111
- return { kind: "bytesIntrinsic", method: write ? "writeNumVar" : "readNumVar", receiver, args, type: F64, loc };
5112
- }
5113
- // DataView getters (DataView maps to bytes<u8>, so the receiver kind
5114
- // and stdlib provenance land here; the checker keeps these names off
5115
- // typed arrays and Buffers). The multi-byte kinds take the optional
5116
- // littleEndian bool (omitted = big-endian, the JS default). All THROW
5117
- // Node's RangeError on a bad offset (may-throw seeds).
5118
- const dvGetter = own(DV_GETTERS, name);
5119
- if (dvGetter !== undefined && receiverIr.elem === "u8") {
5120
- if (dvGetter.method === "dvGetBigUint64Number" || dvGetter.method === "dvGetBigInt64Number") {
5121
- // bigint values have no representation — only the COMPOSED
5122
- // `Number(view.getBigUint64/getBigInt64(...))` compiles (the
5123
- // randomBytesToString pattern): the 8-byte integer converts to
5124
- // double exactly as Number(bigint) would, and the bigint never
5125
- // exists as a value (the f64-typed intrinsic result IS what the
5126
- // identity Number(f64) lowering hands back).
5127
- const p = call.parent;
5128
- const composed = ts.isCallExpression(p) &&
5129
- p.arguments.length === 1 &&
5130
- p.arguments[0] === call &&
5131
- ts.isIdentifier(p.expression) &&
5132
- p.expression.text === "Number" &&
5133
- lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(p.expression) ?? undefined);
5134
- if (!composed) {
5135
- lowerer.noLowering(`.${name} outside Number(...)`, call, `bigint values have no representation — Number(view.${name}(offset, littleEndian?)) is the lowered form`);
5136
- }
5137
- }
5138
- const maxArgs = dvGetter.le ? 2 : 1;
5139
- if (nArgs < 1 || nArgs > maxArgs) {
5140
- lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
5141
- }
5142
- const receiver = lowerer.lowerExpr(access.expression);
5143
- const args = [lowerer.lowerExprExpecting(call.arguments[0], F64)];
5144
- if (nArgs === 2)
5145
- args.push(lowerer.lowerExprExpecting(call.arguments[1], BOOL));
5146
- return { kind: "bytesIntrinsic", method: dvGetter.method, receiver, args, type: F64, loc };
5147
- }
5148
- // DataView setters — the getters' mirror: (byteOffset, value) plus the
5149
- // optional littleEndian bool on the multi-byte kinds. Void results;
5150
- // the same constant Node RangeError on a bad offset (may-throw seeds).
5151
- // setBigUint64/setBigInt64 never lower (bigint ARGUMENTS have no
5152
- // representation and no composed form exists) — they keep the member
5153
- // fence, as does setFloat16.
5154
- const dvSetter = own(DV_SETTERS, name);
5155
- if (dvSetter !== undefined && receiverIr.elem === "u8") {
5156
- const maxArgs = dvSetter.le ? 3 : 2;
5157
- if (nArgs < 2 || nArgs > maxArgs) {
5158
- lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
5159
- }
5160
- const receiver = lowerer.lowerExpr(access.expression);
5161
- const args = [
5162
- lowerer.lowerExprExpecting(call.arguments[0], F64),
5163
- lowerer.lowerExprExpecting(call.arguments[1], F64),
5164
- ];
5165
- if (nArgs === 3)
5166
- args.push(lowerer.lowerExprExpecting(call.arguments[2], BOOL));
5167
- return { kind: "bytesIntrinsic", method: dvSetter.method, receiver, args, type: VOID, loc };
5168
- }
5169
- return null;
5170
- }
5171
- /** Node's Buffer encoding names → the runtime's canonical spelling (every
5172
- * alias folds at compile time; the runtime sees only canonical names).
5173
- * Shared by toString, Buffer.from, and Buffer.byteLength. */
5174
- const BUF_ENCODINGS = {
5175
- utf8: "utf8",
5176
- "utf-8": "utf8",
5177
- hex: "hex",
5178
- base64: "base64",
5179
- base64url: "base64url",
5180
- latin1: "latin1",
5181
- binary: "latin1",
5182
- ascii: "ascii",
5183
- utf16le: "utf16le",
5184
- "utf-16le": "utf16le",
5185
- ucs2: "utf16le",
5186
- "ucs-2": "utf16le",
5187
- };
5188
- /** A literal encoding's canonical name, or undefined for a spelling Node
5189
- * does not know — the ladder callers (stream options) turn unknown
5190
- * literals into Node's runtime ERR_UNKNOWN_ENCODING throw. */
5191
- export function knownBufEncoding(name) {
5192
- return own(BUF_ENCODINGS, name);
5193
- }
5194
- /** Presence guard for a synthetic native-array loop. HOFs skip holes, while
5195
- * the ordinary `arrayGet` remains a proven-presence operation. */
5196
- function arrayHasExpr(arr, index, loc) {
5197
- return {
5198
- kind: "arrayHas",
5199
- arr,
5200
- index,
5201
- type: BOOL,
5202
- loc,
5203
- };
5204
- }
5205
- function getHasExpr(arrT, loc) {
5206
- return arrayHasExpr({ kind: "varRef", localId: "a.0", type: arrT, loc }, { kind: "varRef", localId: "i.0", type: F64, loc }, loc);
5207
- }
5208
- /** The literal encoding argument of a Buffer surface, normalized — or a
5209
- * fence when it isn't a literal alias Node knows. */
5210
- export function bufEncoding(lowerer, what, encNode) {
5211
- const t = lowerer.typeOf(encNode);
5212
- const v = t.isStringLiteralType() ? own(BUF_ENCODINGS, t.value) : undefined;
5213
- if (v === undefined) {
5214
- lowerer.noLowering(`${what} with this encoding`, encNode, 'a literal "utf8", "hex", "base64", "base64url", "latin1", "binary", "ascii", "utf16le", or "ucs2" spelling is the lowered encoding surface');
5215
- }
5216
- return v;
5217
- }
5218
- /** The fixed-width Buffer numeric methods by source name → the readNum/
5219
- * writeNum kind token. Node declares BOTH capitalizations ("UInt" is the
5220
- * original, "Uint" the aliased spelling) — the tables carry both. */
5221
- const BUF_NUM_METHODS = (() => {
5222
- const out = {};
5223
- for (const rw of ["read", "write"]) {
5224
- for (const u of ["UInt", "Uint"]) {
5225
- out[`${rw}${u}8`] = "u8";
5226
- out[`${rw}${u}16BE`] = "u16be";
5227
- out[`${rw}${u}16LE`] = "u16le";
5228
- out[`${rw}${u}32BE`] = "u32be";
5229
- out[`${rw}${u}32LE`] = "u32le";
5230
- }
5231
- out[`${rw}Int8`] = "i8";
5232
- out[`${rw}Int16BE`] = "i16be";
5233
- out[`${rw}Int16LE`] = "i16le";
5234
- out[`${rw}Int32BE`] = "i32be";
5235
- out[`${rw}Int32LE`] = "i32le";
5236
- out[`${rw}FloatBE`] = "f32be";
5237
- out[`${rw}FloatLE`] = "f32le";
5238
- out[`${rw}DoubleBE`] = "f64be";
5239
- out[`${rw}DoubleLE`] = "f64le";
5240
- }
5241
- return out;
5242
- })();
5243
- /** The variable-width quartet (offset + byteLength) → sign/endian token. */
5244
- const BUF_NUM_VAR_METHODS = {
5245
- readUIntBE: "ube",
5246
- readUintBE: "ube",
5247
- readUIntLE: "ule",
5248
- readUintLE: "ule",
5249
- readIntBE: "ibe",
5250
- readIntLE: "ile",
5251
- writeUIntBE: "ube",
5252
- writeUintBE: "ube",
5253
- writeUIntLE: "ule",
5254
- writeUintLE: "ule",
5255
- writeIntBE: "ibe",
5256
- writeIntLE: "ile",
5257
- };
5258
- /** The Buffer-declared comparison/search/mutation methods on a u8
5259
- * receiver: equals, compare, indexOf/lastIndexOf/includes (number,
5260
- * string-with-encoding, and Buffer needles), fill, copy, swap16/32/64,
5261
- * and write. Null when `name` isn't one of them (the numeric families
5262
- * and the generic bytes surface try next). Encodings must be literals
5263
- * (bufEncoding's fence); a trailing string-typed argument is the
5264
- * encoding, exactly Node's overloads. */
5265
- function lowerBufferInstanceMethod(lowerer, call, access, name, loc) {
5266
- const args = call.arguments;
5267
- const nArgs = args.length;
5268
- if (args.some(ts.isSpreadElement))
5269
- return null;
5270
- const isStringArg = (i) => lowerer.mapTypeOf(lowerer.typeOf(args[i]))?.kind === "string";
5271
- const u8Arg = (i) => {
5272
- const v = lowerer.lowerExpr(args[i]);
5273
- if (!(v.type.kind === "bytes" && v.type.elem === "u8")) {
5274
- lowerer.noLowering(`.${name} of '${lowerer.fmt(v.type)}' values`, args[i], "a Buffer/Uint8Array argument is the lowered shape (narrow unions first)");
5275
- }
5276
- return v;
5277
- };
5278
- // The checked-dynamic crossing for a compare/equals argument slot: dyn
5279
- // passes through, convertible statics (the invalid-input probes'
5280
- // literals) wrap in dynFrom, `undefined` literals ride the same wrap.
5281
- // Fences (never returns) when the value cannot cross — an island jsval.
5282
- const chkArg = (i) => {
5283
- // Object literals take the dyn literal path directly (method members
5284
- // box as dyn functions — the typed record fence never applies).
5285
- if (ts.isObjectLiteralExpression(args[i])) {
5286
- return lowerDynObjectLiteral(lowerer, args[i]);
5287
- }
5288
- const v = lowerer.lowerExpr(args[i]);
5289
- if (v.type.kind === "dyn")
5290
- return v;
5291
- if (v.kind === "unitLit" || lowerer.dynConvertible(v.type)) {
5292
- return { kind: "dynFrom", value: v, type: DYN, loc: v.loc };
5293
- }
5294
- lowerer.noLowering(`.${name} of '${lowerer.fmt(v.type)}' values`, args[i], "a Buffer/Uint8Array argument is the lowered shape (narrow unions first)");
5295
- };
5296
- const argIrKind = (i) => lowerer.mapTypeOf(lowerer.typeOf(args[i]));
5297
- if (name === "equals") {
5298
- if (nArgs !== 1)
5299
- lowerer.noLowering(`.equals with ${nArgs} arguments`, call);
5300
- if (argIrKind(0)?.kind === "bytes") {
5301
- const other = u8Arg(0);
5302
- const receiver = lowerer.lowerExpr(access.expression);
5303
- return { kind: "bytesIntrinsic", method: "equals", receiver, args: [other], type: BOOL, loc };
5304
- }
5305
- // Not statically bytes (the invalid-input probes, untyped JS
5306
- // helpers): Node's "otherBuffer" argument ladder runs at runtime.
5307
- const receiver = lowerer.lowerExpr(access.expression);
5308
- return { kind: "libCall", fn: "bytes.equalsChk", args: [receiver, chkArg(0)], type: BOOL, loc };
5309
- }
5310
- if (name === "compare") {
5311
- if (nArgs > 5)
5312
- lowerer.noLowering(`.compare with ${nArgs} arguments`, call);
5313
- const fast = nArgs >= 1 &&
5314
- argIrKind(0)?.kind === "bytes" &&
5315
- call.arguments.slice(1).every((a) => lowerer.mapTypeOf(lowerer.typeOf(a))?.kind === "f64");
5316
- if (fast) {
5317
- const target = u8Arg(0);
5318
- const idx = call.arguments.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
5319
- const receiver = lowerer.lowerExpr(access.expression);
5320
- return { kind: "bytesIntrinsic", method: "compareBuf", receiver, args: [target, ...idx], type: F64, loc };
5321
- }
5322
- // An absent/ill-typed target or offset (`a.compare()`, string
5323
- // offsets, explicit undefined): Node's target/targetStart/targetEnd/
5324
- // sourceStart/sourceEnd ladder runs at runtime; absent slots pass
5325
- // the undefined dyn (Node defaults apply there).
5326
- const receiver = lowerer.lowerExpr(access.expression);
5327
- const slots = [];
5328
- for (let i = 0; i < 5; i++)
5329
- slots.push(i < nArgs ? chkArg(i) : dynUndefinedExpr(loc));
5330
- return { kind: "libCall", fn: "bytes.compareChk", args: [receiver, ...slots], type: F64, loc };
5331
- }
5332
- if (name === "indexOf" || name === "lastIndexOf" || name === "includes") {
5333
- if (nArgs < 1 || nArgs > 3)
5334
- lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
5335
- // The overloads: (v), (v, byteOffset), (v, encoding), (v, byteOffset,
5336
- // encoding) — a trailing string-typed arg is the encoding.
5337
- let encName = "utf8";
5338
- let offNode;
5339
- if (nArgs === 3) {
5340
- offNode = args[1];
5341
- encName = bufEncoding(lowerer, `.${name}`, args[2]);
5342
- }
5343
- else if (nArgs === 2) {
5344
- if (isStringArg(1))
5345
- encName = bufEncoding(lowerer, `.${name}`, args[1]);
5346
- else
5347
- offNode = args[1];
5348
- }
5349
- const resultT = name === "includes" ? BOOL : F64;
5350
- const vT = lowerer.mapTypeOf(lowerer.typeOf(args[0]));
5351
- const receiver = lowerer.lowerExpr(access.expression);
5352
- if (vT?.kind === "f64") {
5353
- // A number needle wraps & 0xFF at runtime (Buffer semantics; the
5354
- // encoding is irrelevant, like Node).
5355
- const v = lowerer.lowerExprExpecting(args[0], F64);
5356
- const numArgs = [v, ...(offNode ? [lowerer.lowerExprExpecting(offNode, F64)] : [])];
5357
- const method = name === "indexOf" ? "indexOfNum" : name === "lastIndexOf" ? "lastIndexOfNum" : "includesNum";
5358
- return { kind: "bytesIntrinsic", method, receiver, args: numArgs, type: resultT, loc };
5359
- }
5360
- let needle;
5361
- let align = 1;
5362
- if (vT?.kind === "string") {
5363
- const s = lowerer.lowerExprExpecting(args[0], STRING);
5364
- const enc = { kind: "strLit", value: encName, type: STRING, loc };
5365
- needle = { kind: "libCall", fn: "buffer.fromStr", args: [s, enc], type: BYTES_U8, loc };
5366
- if (encName === "utf16le")
5367
- align = 2;
5368
- }
5369
- else if (vT?.kind === "bytes" && vT.elem === "u8") {
5370
- needle = lowerer.lowerExpr(args[0]);
5371
- }
5372
- else {
5373
- lowerer.noLowering(`.${name} of '${lowerer.checker.typeToString(lowerer.typeOf(args[0]))}' values`, args[0], "string, number, and Buffer/Uint8Array needles search (narrow unions first)");
5374
- }
5375
- const alignLit = { kind: "numLit", value: align, type: F64, loc };
5376
- const searchArgs = [needle, alignLit, ...(offNode ? [lowerer.lowerExprExpecting(offNode, F64)] : [])];
5377
- return { kind: "bytesIntrinsic", method: name, receiver, args: searchArgs, type: resultT, loc };
5378
- }
5379
- if (name === "fill") {
5380
- if (nArgs < 1 || nArgs > 4)
5381
- lowerer.noLowering(`.fill with ${nArgs} arguments`, call);
5382
- // A trailing string-typed arg past the value is the encoding.
5383
- let encNode;
5384
- let idxNodes = call.arguments.slice(1);
5385
- if (idxNodes.length > 0 && isStringArg(nArgs - 1)) {
5386
- encNode = idxNodes[idxNodes.length - 1];
5387
- idxNodes = idxNodes.slice(0, -1);
5388
- }
5389
- if (idxNodes.length > 2)
5390
- lowerer.noLowering(`.fill with ${nArgs} arguments`, call);
5391
- const idx = idxNodes.map((a) => lowerer.lowerExprExpecting(a, F64));
5392
- const receiverT = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
5393
- if (receiverT?.kind !== "bytes")
5394
- lowerer.badType(access.expression, lowerer.typeOf(access.expression));
5395
- const vT = lowerer.mapTypeOf(lowerer.typeOf(args[0]));
5396
- const receiver = lowerer.lowerExpr(access.expression);
5397
- if (vT?.kind === "string") {
5398
- const encName = encNode ? bufEncoding(lowerer, ".fill", encNode) : "utf8";
5399
- const s = lowerer.lowerExprExpecting(args[0], STRING);
5400
- const enc = { kind: "strLit", value: encName, type: STRING, loc };
5401
- return { kind: "bytesIntrinsic", method: "fillStr", receiver, args: [s, enc, ...idx], type: BYTES_U8, loc };
5402
- }
5403
- if (encNode)
5404
- lowerer.noLowering(".fill with an encoding on a non-string value", encNode);
5405
- if (vT?.kind === "f64") {
5406
- const v = lowerer.lowerExprExpecting(args[0], F64);
5407
- return { kind: "bytesIntrinsic", method: "fillNum", receiver, args: [v, ...idx], type: BYTES_U8, loc };
5408
- }
5409
- const pattern = u8Arg(0);
5410
- return { kind: "bytesIntrinsic", method: "fill", receiver, args: [pattern, ...idx], type: BYTES_U8, loc };
5411
- }
5412
- if (name === "copy") {
5413
- if (nArgs < 1 || nArgs > 4)
5414
- lowerer.noLowering(`.copy with ${nArgs} arguments`, call);
5415
- const target = u8Arg(0);
5416
- const idx = call.arguments.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
5417
- const receiver = lowerer.lowerExpr(access.expression);
5418
- return { kind: "bytesIntrinsic", method: "copy", receiver, args: [target, ...idx], type: F64, loc };
5419
- }
5420
- if (name === "swap16" || name === "swap32" || name === "swap64") {
5421
- if (nArgs !== 0)
5422
- lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
5423
- const receiver = lowerer.lowerExpr(access.expression);
5424
- return { kind: "bytesIntrinsic", method: name, receiver, args: [], type: BYTES_U8, loc };
5425
- }
5426
- if (name === "write") {
5427
- if (nArgs < 1 || nArgs > 4)
5428
- lowerer.noLowering(`.write with ${nArgs} arguments`, call);
5429
- // (str), (str, enc), (str, offset), (str, offset, enc),
5430
- // (str, offset, length), (str, offset, length, enc).
5431
- let encNode;
5432
- let idxNodes = call.arguments.slice(1);
5433
- if (idxNodes.length > 0 && isStringArg(nArgs - 1)) {
5434
- encNode = idxNodes[idxNodes.length - 1];
5435
- idxNodes = idxNodes.slice(0, -1);
5436
- }
5437
- if (idxNodes.length > 2)
5438
- lowerer.noLowering(`.write with ${nArgs} arguments`, call);
5439
- const encName = encNode ? bufEncoding(lowerer, "Buffer.write", encNode) : "utf8";
5440
- const s = lowerer.lowerExprExpecting(args[0], STRING);
5441
- const enc = { kind: "strLit", value: encName, type: STRING, loc };
5442
- const offset = idxNodes[0]
5443
- ? lowerer.lowerExprExpecting(idxNodes[0], F64)
5444
- : { kind: "numLit", value: 0, type: F64, loc };
5445
- const writeArgs = [s, enc, offset, ...(idxNodes[1] ? [lowerer.lowerExprExpecting(idxNodes[1], F64)] : [])];
5446
- const receiver = lowerer.lowerExpr(access.expression);
5447
- return { kind: "bytesIntrinsic", method: "writeStr", receiver, args: writeArgs, type: F64, loc };
5448
- }
5449
- return null;
5450
- }
5451
- /** The DataView getter surface by source name. `le` marks the multi-byte
5452
- * kinds whose lib signature declares the optional littleEndian parameter
5453
- * (the 8-bit getters take none). The Big pair only compiles composed
5454
- * inside Number(...) — see the use site. */
5455
- const DV_GETTERS = {
5456
- getUint8: { method: "dvGetUint8", le: false },
5457
- getInt8: { method: "dvGetInt8", le: false },
5458
- getUint16: { method: "dvGetUint16", le: true },
5459
- getInt16: { method: "dvGetInt16", le: true },
5460
- getUint32: { method: "dvGetUint32", le: true },
5461
- getInt32: { method: "dvGetInt32", le: true },
5462
- getFloat32: { method: "dvGetFloat32", le: true },
5463
- getFloat64: { method: "dvGetFloat64", le: true },
5464
- getBigUint64: { method: "dvGetBigUint64Number", le: true },
5465
- getBigInt64: { method: "dvGetBigInt64Number", le: true },
5466
- };
5467
- const DV_SETTERS = {
5468
- setUint8: { method: "dvSetUint8", le: false },
5469
- setInt8: { method: "dvSetInt8", le: false },
5470
- setUint16: { method: "dvSetUint16", le: true },
5471
- setInt16: { method: "dvSetInt16", le: true },
5472
- setUint32: { method: "dvSetUint32", le: true },
5473
- setInt32: { method: "dvSetInt32", le: true },
5474
- setFloat32: { method: "dvSetFloat32", le: true },
5475
- setFloat64: { method: "dvSetFloat64", le: true },
5476
- };
5477
- /** The Buffer statics — `Buffer.from(...)`, `Buffer.alloc(n)`,
5478
- * `Buffer.concat(list)`, `Buffer.isBuffer(x)` — on THE stdlib Buffer
5479
- * global (name + provenance; fallback and @types/node alike). Null when
5480
- * the callee isn't a Buffer-static access. */
5481
- export function lowerBufferStaticCall(lowerer, call, access) {
5482
- if (call.questionDotToken || access.questionDotToken)
5483
- return null;
5484
- if (!lowerer.isStdlibGlobal(access.expression, "Buffer"))
5485
- return null;
5486
- const member = access.name.text;
5487
- const loc = locOf(call);
5488
- const args = call.arguments;
5489
- if (member === "from") {
5490
- // Buffer.from(x.buffer[, byteOffset[, length]]): a u8 VIEW sharing
5491
- // x's storage (Node shares the ArrayBuffer; length is in BYTES).
5492
- // The first argument must be the SYNTACTIC `.buffer` of a typed
5493
- // array — the DataView peel — and the construction rides the same
5494
- // dataViewNew intrinsic (offset/length validation throws Node's
5495
- // DataView-shaped RangeErrors; valid indices behave exactly).
5496
- if (args.length >= 1 && args.length <= 3 && !args.some(ts.isSpreadElement) &&
5497
- ts.isPropertyAccessExpression(args[0]) && args[0].name.text === "buffer") {
5498
- const srcNode = args[0].expression;
5499
- const srcIr = lowerer.mapTypeOf(lowerer.typeOf(srcNode));
5500
- if (srcIr?.kind === "bytes") {
5501
- const receiver = lowerer.lowerExpr(srcNode);
5502
- const idxArgs = args.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
5503
- return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
5504
- }
5505
- }
5506
- if (args.length >= 1 && args.length <= 2 && !ts.isSpreadElement(args[0])) {
5507
- const argNode = args[0];
5508
- if (ts.isArrayLiteralExpression(argNode) && !argNode.elements.some(ts.isSpreadElement)) {
5509
- // A number[] literal (contextually typed by the lib's readonly
5510
- // number[] slot — build it element-wise, the Set-seed pattern).
5511
- if (args.length === 1) {
5512
- const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, F64));
5513
- const seed = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
5514
- return { kind: "bytesNew", source: seed, type: BYTES_U8, loc };
5515
- }
5516
- }
5517
- else {
5518
- const srcIr = lowerer.mapTypeOf(lowerer.typeOf(argNode));
5519
- if (srcIr?.kind === "string") {
5520
- const encNode = args[1];
5521
- const encName = encNode ? bufEncoding(lowerer, "Buffer.from", encNode) : "utf8";
5522
- const s = lowerer.lowerExprExpecting(argNode, STRING);
5523
- const enc = { kind: "strLit", value: encName, type: STRING, loc };
5524
- return { kind: "libCall", fn: "buffer.fromStr", args: [s, enc], type: BYTES_U8, loc };
5525
- }
5526
- if (args.length === 1 && srcIr?.kind === "bytes" && srcIr.elem === "u8") {
5527
- return { kind: "bytesNew", source: lowerer.lowerExpr(argNode), type: BYTES_U8, loc };
5528
- }
5529
- if (args.length === 1 && srcIr?.kind === "array" && srcIr.elem.kind === "f64") {
5530
- return { kind: "bytesNew", source: lowerer.lowerExpr(argNode), type: BYTES_U8, loc };
5531
- }
5532
- }
5533
- }
5534
- lowerer.noLowering("Buffer.from with this argument shape", call, "supported: Buffer.from(string, encoding?) with a literal encoding, Buffer.from(u8Array) — a copy — " +
5535
- "Buffer.from(number[]), or Buffer.from(x.buffer, byteOffset?, length?) — a view sharing x's " +
5536
- "storage (no free-standing ArrayBuffer value exists; narrow unions first)");
5537
- }
5538
- if (member === "alloc" || member === "allocUnsafe") {
5539
- const maxArgs = member === "alloc" ? 3 : 1;
5540
- if (args.length < 1 || args.length > maxArgs || args.some(ts.isSpreadElement)) {
5541
- lowerer.noLowering(`Buffer.${member} with ${args.length} arguments`, call);
5542
- }
5543
- // allocUnsafe's contents are UNSPECIFIED in Node (uninitialized pool
5544
- // memory); a zero-filled buffer is a valid instance of unspecified,
5545
- // and the deterministic choice — a program observing the difference
5546
- // is depending on garbage.
5547
- const size = lowerer.lowerExprExpecting(args[0], F64);
5548
- const fresh = { kind: "bytesNew", source: size, type: BYTES_U8, loc };
5549
- if (args.length === 1)
5550
- return fresh;
5551
- // alloc(size, fill, encoding?): the fill semantics ARE fill()'s —
5552
- // the fresh buffer is the receiver of a whole-range fill.
5553
- const fillT = lowerer.mapTypeOf(lowerer.typeOf(args[1]));
5554
- if (fillT?.kind === "string") {
5555
- const encName = args[2] ? bufEncoding(lowerer, "Buffer.alloc", args[2]) : "utf8";
5556
- const s = lowerer.lowerExprExpecting(args[1], STRING);
5557
- const enc = { kind: "strLit", value: encName, type: STRING, loc };
5558
- return { kind: "bytesIntrinsic", method: "fillStr", receiver: fresh, args: [s, enc], type: BYTES_U8, loc };
5559
- }
5560
- if (args.length > 2) {
5561
- lowerer.noLowering("Buffer.alloc with an encoding on a non-string fill", args[2]);
5562
- }
5563
- if (fillT?.kind === "f64") {
5564
- const v = lowerer.lowerExprExpecting(args[1], F64);
5565
- return { kind: "bytesIntrinsic", method: "fillNum", receiver: fresh, args: [v], type: BYTES_U8, loc };
5566
- }
5567
- if (fillT?.kind === "bytes" && fillT.elem === "u8") {
5568
- const pattern = lowerer.lowerExpr(args[1]);
5569
- return { kind: "bytesIntrinsic", method: "fill", receiver: fresh, args: [pattern], type: BYTES_U8, loc };
5570
- }
5571
- lowerer.noLowering(`Buffer.alloc with a '${lowerer.checker.typeToString(lowerer.typeOf(args[1]))}' fill`, args[1], "number, string, and Buffer/Uint8Array fills are the lowered shapes");
5572
- }
5573
- if (member === "concat") {
5574
- if (args.length < 1 || args.length > 2 || args.some(ts.isSpreadElement)) {
5575
- lowerer.noLowering(`Buffer.concat with ${args.length} arguments`, call);
5576
- }
5577
- const argNode = args[0];
5578
- let list;
5579
- if (ts.isArrayLiteralExpression(argNode) && !argNode.elements.some(ts.isSpreadElement)) {
5580
- const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, BYTES_U8));
5581
- list = { kind: "arrayLit", elems, type: arrayOf(BYTES_U8), loc };
5582
- }
5583
- else {
5584
- list = lowerer.lowerExpr(argNode);
5585
- if (!typeEquals(list.type, arrayOf(BYTES_U8))) {
5586
- lowerer.noLowering(`Buffer.concat of '${lowerer.fmt(list.type)}' values`, argNode, "one Uint8Array[]/Buffer[] value (or literal) is the lowered list shape");
5587
- }
5588
- }
5589
- if (args.length === 2) {
5590
- // The totalLength form truncates or zero-pads (and THROWS Node's
5591
- // 'length' RangeError on bad totals).
5592
- const total = lowerer.lowerExprExpecting(args[1], F64);
5593
- return { kind: "libCall", fn: "buffer.concatLen", args: [list, total], type: BYTES_U8, loc };
5594
- }
5595
- return { kind: "libCall", fn: "buffer.concat", args: [list], type: BYTES_U8, loc };
5596
- }
5597
- if (member === "compare") {
5598
- // The static form: Buffer.compare(a, b) IS a.compare(b).
5599
- if (args.length !== 2 || args.some(ts.isSpreadElement)) {
5600
- lowerer.noLowering(`Buffer.compare with ${args.length} arguments`, call);
5601
- }
5602
- const sides = args.map((a) => lowerer.lowerExpr(a));
5603
- if (sides.every((v) => v.type.kind === "bytes" && v.type.elem === "u8")) {
5604
- return { kind: "bytesIntrinsic", method: "compareBuf", receiver: sides[0], args: [sides[1]], type: F64, loc };
5605
- }
5606
- // A side that is not statically bytes (the invalid-input probes,
5607
- // untyped JS helpers): Node's "buf1"/"buf2" argument ladder runs
5608
- // at runtime — a well-typed dyn still compares.
5609
- const dyns = sides.map((v, i) => {
5610
- if (v.type.kind === "dyn")
5611
- return v;
5612
- if (v.kind === "unitLit" || lowerer.dynConvertible(v.type)) {
5613
- return { kind: "dynFrom", value: v, type: DYN, loc: v.loc };
5614
- }
5615
- lowerer.noLowering(`Buffer.compare of '${lowerer.fmt(v.type)}' values`, args[i], "Buffer/Uint8Array values compare (narrow unions first)");
5616
- });
5617
- return { kind: "libCall", fn: "buffer.compareChk", args: dyns, type: F64, loc };
5618
- }
5619
- if (member === "isBuffer") {
5620
- // The type-predicate narrowing test. Lowered where it DECIDES
5621
- // something: a union-typed argument with a Buffer arm becomes a
5622
- // runtime tag test (tsc's narrowing then types the branches, the
5623
- // discriminated-union machinery). Statically-decided arguments are
5624
- // fenced — the answer is a constant, and the operand's evaluation
5625
- // would have to be discarded.
5626
- if (args.length === 1 && !ts.isSpreadElement(args[0])) {
5627
- const v = lowerer.lowerExpr(args[0]);
5628
- if (v.type.kind === "union") {
5629
- const def = lowerer.unions.get(v.type.unionId);
5630
- const tag = def ? def.arms.findIndex((a) => a.kind === "bytes" && a.elem === "u8") : -1;
5631
- if (tag >= 0) {
5632
- return { kind: "unionIsTag", unionId: v.type.unionId, tag, negated: false, value: v, type: BOOL, loc };
5633
- }
5634
- }
5635
- lowerer.noLowering(`Buffer.isBuffer of '${lowerer.fmt(v.type)}' values`, args[0], "the check lowers as a runtime tag test on unions with a Buffer arm — here the answer is static");
5636
- }
5637
- lowerer.noLowering("Buffer.isBuffer with this argument shape", call);
5638
- }
5639
- if (member === "byteLength") {
5640
- // Buffer.byteLength(string, enc?) — the UTF-16-aware per-encoding
5641
- // count — or of a typed array/Buffer (its byte length, a property
5642
- // read at heart).
5643
- if (args.length >= 1 && args.length <= 2 && !ts.isSpreadElement(args[0])) {
5644
- const srcIr = lowerer.mapTypeOf(lowerer.typeOf(args[0]));
5645
- if (srcIr?.kind === "string") {
5646
- const encName = args[1] ? bufEncoding(lowerer, "Buffer.byteLength", args[1]) : "utf8";
5647
- const s = lowerer.lowerExprExpecting(args[0], STRING);
5648
- const enc = { kind: "strLit", value: encName, type: STRING, loc };
5649
- return { kind: "libCall", fn: "buffer.byteLenStr", args: [s, enc], type: F64, loc };
5650
- }
5651
- if (srcIr?.kind === "bytes" && args.length === 1) {
5652
- const receiver = lowerer.lowerExpr(args[0]);
5653
- return { kind: "bytesIntrinsic", method: "byteLength", receiver, args: [], type: F64, loc };
5654
- }
5655
- }
5656
- lowerer.noLowering("Buffer.byteLength with this argument shape", call, "supported: Buffer.byteLength(string, literalEncoding?) or Buffer.byteLength(typedArray)");
5657
- }
5658
- if (member === "isEncoding") {
5659
- // The runtime alias-set test — a plain bool over any string value
5660
- // (Node's case-insensitive normalizeEncoding check).
5661
- if (args.length === 1 && !ts.isSpreadElement(args[0]) && lowerer.mapTypeOf(lowerer.typeOf(args[0]))?.kind === "string") {
5662
- const s = lowerer.lowerExprExpecting(args[0], STRING);
5663
- return { kind: "libCall", fn: "buffer.isEncoding", args: [s], type: BOOL, loc };
5664
- }
5665
- lowerer.noLowering("Buffer.isEncoding with this argument shape", call, "one string-typed argument is the lowered form");
5666
- }
5667
- return null; // of, copyBytesFrom, ... → the SC2020 member fence
5668
- }
5669
4341
  /** `Object.keys/values/entries` over an INDEX-SIGNATURE (overflow-carrying)
5670
4342
  * record shape: declared fields answer first from the compile-time field
5671
4343
  * list (declaration order, undefined-valued fields skipped — exactly the