@scriptc/compiler 0.0.36 → 0.1.2

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 (256) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +52 -22
  2. package/ambient/scriptc-overrides.d.ts +0 -7
  3. package/ambient/scriptc.d.ts +3 -2
  4. package/dist/backend/build-cache.js.map +1 -1
  5. package/dist/backend/c/async.js +24 -2
  6. package/dist/backend/c/async.js.map +1 -1
  7. package/dist/backend/c/c-emitter.d.ts +22 -10
  8. package/dist/backend/c/c-emitter.js +60 -15
  9. package/dist/backend/c/c-emitter.js.map +1 -1
  10. package/dist/backend/c/exprs.js +289 -55
  11. package/dist/backend/c/exprs.js.map +1 -1
  12. package/dist/backend/c/island.js.map +1 -1
  13. package/dist/backend/c/may-throw.js +3 -0
  14. package/dist/backend/c/may-throw.js.map +1 -1
  15. package/dist/backend/c/stmts.d.ts +3 -5
  16. package/dist/backend/c/stmts.js +76 -17
  17. package/dist/backend/c/stmts.js.map +1 -1
  18. package/dist/backend/c/types.js.map +1 -1
  19. package/dist/backend/c/walkers.js +36 -12
  20. package/dist/backend/c/walkers.js.map +1 -1
  21. package/dist/backend/external-c.d.ts +33 -0
  22. package/dist/backend/external-c.js +51 -0
  23. package/dist/backend/external-c.js.map +1 -0
  24. package/dist/backend/link-plan.d.ts +37 -0
  25. package/dist/backend/link-plan.js +26 -0
  26. package/dist/backend/link-plan.js.map +1 -0
  27. package/dist/backend/linker.d.ts +22 -0
  28. package/dist/backend/linker.js +236 -0
  29. package/dist/backend/linker.js.map +1 -0
  30. package/dist/backend/llvm/blocks.js.map +1 -1
  31. package/dist/backend/llvm/dyn.js +63 -3
  32. package/dist/backend/llvm/dyn.js.map +1 -1
  33. package/dist/backend/llvm/emitter.js +132 -26
  34. package/dist/backend/llvm/emitter.js.map +1 -1
  35. package/dist/backend/llvm/expr-async.js +85 -3
  36. package/dist/backend/llvm/expr-async.js.map +1 -1
  37. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  38. package/dist/backend/llvm/expr-calls.js.map +1 -1
  39. package/dist/backend/llvm/expr-containers.js +68 -54
  40. package/dist/backend/llvm/expr-containers.js.map +1 -1
  41. package/dist/backend/llvm/expr-context.d.ts +2 -1
  42. package/dist/backend/llvm/expr-control.js.map +1 -1
  43. package/dist/backend/llvm/expr-dispatch.js +2 -0
  44. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  45. package/dist/backend/llvm/expr-dynamic.js +6 -3
  46. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  47. package/dist/backend/llvm/expr-island.js.map +1 -1
  48. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  49. package/dist/backend/llvm/expr-primitives.js +20 -0
  50. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  51. package/dist/backend/llvm/expr-records.js.map +1 -1
  52. package/dist/backend/llvm/expr-stream-bridges.js +245 -10
  53. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  54. package/dist/backend/llvm/lib-dispatch.js +2 -0
  55. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  56. package/dist/backend/llvm/lib-shared.js +18 -0
  57. package/dist/backend/llvm/lib-shared.js.map +1 -1
  58. package/dist/backend/llvm/lib-stream.js +6 -3
  59. package/dist/backend/llvm/lib-stream.js.map +1 -1
  60. package/dist/backend/llvm/split.js.map +1 -1
  61. package/dist/backend/llvm/unsupported.js.map +1 -1
  62. package/dist/backend/llvm/walkers.js +31 -4
  63. package/dist/backend/llvm/walkers.js.map +1 -1
  64. package/dist/backend/native-codegen.d.ts +8 -1
  65. package/dist/backend/native-codegen.js +39 -25
  66. package/dist/backend/native-codegen.js.map +1 -1
  67. package/dist/backend/native-codegen.test.js +44 -4
  68. package/dist/backend/native-codegen.test.js.map +1 -1
  69. package/dist/backend/native-link-info.d.ts +1 -1
  70. package/dist/backend/native-link-info.js +42 -18
  71. package/dist/backend/native-link-info.js.map +1 -1
  72. package/dist/backend/native-toolchain.js.map +1 -1
  73. package/dist/backend/native-toolchain.test.js +5 -1
  74. package/dist/backend/native-toolchain.test.js.map +1 -1
  75. package/dist/backend/runtime-pack-targets.test.d.ts +1 -0
  76. package/dist/backend/runtime-pack-targets.test.js +54 -0
  77. package/dist/backend/runtime-pack-targets.test.js.map +1 -0
  78. package/dist/backend/runtime-pack.d.ts +5 -30
  79. package/dist/backend/runtime-pack.js +46 -141
  80. package/dist/backend/runtime-pack.js.map +1 -1
  81. package/dist/backend/runtime-pack.test.js +49 -14
  82. package/dist/backend/runtime-pack.test.js.map +1 -1
  83. package/dist/backend/targets.d.ts +63 -17
  84. package/dist/backend/targets.js +251 -35
  85. package/dist/backend/targets.js.map +1 -1
  86. package/dist/backend/targets.test.js +27 -5
  87. package/dist/backend/targets.test.js.map +1 -1
  88. package/dist/backend/vendor-archives.js.map +1 -1
  89. package/dist/compat/fetch-profile.js +4 -2
  90. package/dist/compat/fetch-profile.js.map +1 -1
  91. package/dist/coverage/report.js.map +1 -1
  92. package/dist/coverage/surface-manifest.js +22 -1
  93. package/dist/coverage/surface-manifest.js.map +1 -1
  94. package/dist/diagnostics/diagnostic.d.ts +6 -5
  95. package/dist/diagnostics/diagnostic.js +7 -6
  96. package/dist/diagnostics/diagnostic.js.map +1 -1
  97. package/dist/diagnostics/render.js.map +1 -1
  98. package/dist/ffi/ffi-manifest.js.map +1 -1
  99. package/dist/frontend/cjs-lexer.js.map +1 -1
  100. package/dist/frontend/dts-paths.js.map +1 -1
  101. package/dist/frontend/input-tracker.d.ts +10 -0
  102. package/dist/frontend/input-tracker.js +26 -3
  103. package/dist/frontend/input-tracker.js.map +1 -1
  104. package/dist/frontend/input-tracker.test.js +8 -1
  105. package/dist/frontend/input-tracker.test.js.map +1 -1
  106. package/dist/frontend/lib-contract.js.map +1 -1
  107. package/dist/frontend/lowering/array-values.d.ts +12 -0
  108. package/dist/frontend/lowering/array-values.js +76 -0
  109. package/dist/frontend/lowering/array-values.js.map +1 -0
  110. package/dist/frontend/lowering/callback-arg.js.map +1 -1
  111. package/dist/frontend/lowering/containers/bytes.d.ts +35 -0
  112. package/dist/frontend/lowering/containers/bytes.js +1021 -0
  113. package/dist/frontend/lowering/containers/bytes.js.map +1 -0
  114. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +17 -0
  115. package/dist/frontend/lowering/containers/string-and-regexp.js +345 -0
  116. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -0
  117. package/dist/frontend/lowering/expressions/evaluation-safety.d.ts +13 -0
  118. package/dist/frontend/lowering/expressions/evaluation-safety.js +76 -0
  119. package/dist/frontend/lowering/expressions/evaluation-safety.js.map +1 -0
  120. package/dist/frontend/lowering/expressions/object-literals.d.ts +44 -0
  121. package/dist/frontend/lowering/expressions/object-literals.js +1815 -0
  122. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -0
  123. package/dist/frontend/lowering/expressions/optional-chains.d.ts +31 -0
  124. package/dist/frontend/lowering/expressions/optional-chains.js +382 -0
  125. package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -0
  126. package/dist/frontend/lowering/expressions/try-lower-expression.d.ts +7 -0
  127. package/dist/frontend/lowering/expressions/try-lower-expression.js +26 -0
  128. package/dist/frontend/lowering/expressions/try-lower-expression.js.map +1 -0
  129. package/dist/frontend/lowering/lib-boundary.js +7 -2
  130. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  131. package/dist/frontend/lowering/lower-array-sort.d.ts +22 -0
  132. package/dist/frontend/lowering/lower-array-sort.js +619 -0
  133. package/dist/frontend/lowering/lower-array-sort.js.map +1 -0
  134. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  135. package/dist/frontend/lowering/lower-async-iteration.d.ts +7 -0
  136. package/dist/frontend/lowering/lower-async-iteration.js +657 -0
  137. package/dist/frontend/lowering/lower-async-iteration.js.map +1 -0
  138. package/dist/frontend/lowering/lower-builtins.d.ts +18 -0
  139. package/dist/frontend/lowering/lower-builtins.js +379 -48
  140. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  141. package/dist/frontend/lowering/lower-calls.d.ts +16 -8
  142. package/dist/frontend/lowering/lower-calls.js +667 -91
  143. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  144. package/dist/frontend/lowering/lower-classes.d.ts +7 -13
  145. package/dist/frontend/lowering/lower-classes.js +128 -35
  146. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  147. package/dist/frontend/lowering/lower-comptime.js.map +1 -1
  148. package/dist/frontend/lowering/lower-containers.d.ts +1 -47
  149. package/dist/frontend/lowering/lower-containers.js +779 -2038
  150. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  151. package/dist/frontend/lowering/lower-event-emitter.js +51 -5
  152. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  153. package/dist/frontend/lowering/lower-exprs.d.ts +4 -78
  154. package/dist/frontend/lowering/lower-exprs.js +1128 -2409
  155. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  156. package/dist/frontend/lowering/lower-generators.d.ts +18 -3
  157. package/dist/frontend/lowering/lower-generators.js +145 -9
  158. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  159. package/dist/frontend/lowering/lower-inspect.js +50 -36
  160. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  161. package/dist/frontend/lowering/lower-island.d.ts +11 -4
  162. package/dist/frontend/lowering/lower-island.js +47 -10
  163. package/dist/frontend/lowering/lower-island.js.map +1 -1
  164. package/dist/frontend/lowering/lower-modules.js +52 -15
  165. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  166. package/dist/frontend/lowering/lower-node-module.d.ts +18 -0
  167. package/dist/frontend/lowering/lower-node-module.js +302 -0
  168. package/dist/frontend/lowering/lower-node-module.js.map +1 -0
  169. package/dist/frontend/lowering/lower-server.js +11 -1
  170. package/dist/frontend/lowering/lower-server.js.map +1 -1
  171. package/dist/frontend/lowering/lower-stmts.d.ts +5 -24
  172. package/dist/frontend/lowering/lower-stmts.js +811 -314
  173. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  174. package/dist/frontend/lowering/lower-stream.js +4 -4
  175. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  176. package/dist/frontend/lowering/lowerer.d.ts +109 -34
  177. package/dist/frontend/lowering/lowerer.js +1225 -62
  178. package/dist/frontend/lowering/lowerer.js.map +1 -1
  179. package/dist/frontend/lowering/optional-arguments.d.ts +9 -0
  180. package/dist/frontend/lowering/optional-arguments.js +57 -0
  181. package/dist/frontend/lowering/optional-arguments.js.map +1 -0
  182. package/dist/frontend/lowering/surfaces.d.ts +10 -2
  183. package/dist/frontend/lowering/surfaces.js +15 -3
  184. package/dist/frontend/lowering/surfaces.js.map +1 -1
  185. package/dist/frontend/npm-static-declarations.d.ts +35 -0
  186. package/dist/frontend/npm-static-declarations.js +506 -0
  187. package/dist/frontend/npm-static-declarations.js.map +1 -0
  188. package/dist/frontend/npm-static-declarations.test.d.ts +1 -0
  189. package/dist/frontend/npm-static-declarations.test.js +122 -0
  190. package/dist/frontend/npm-static-declarations.test.js.map +1 -0
  191. package/dist/frontend/npm-static.d.ts +2 -0
  192. package/dist/frontend/npm-static.js +26 -10
  193. package/dist/frontend/npm-static.js.map +1 -1
  194. package/dist/frontend/npm.d.ts +60 -0
  195. package/dist/frontend/npm.js +38 -4
  196. package/dist/frontend/npm.js.map +1 -1
  197. package/dist/frontend/npm.test.d.ts +1 -0
  198. package/dist/frontend/npm.test.js +50 -0
  199. package/dist/frontend/npm.test.js.map +1 -0
  200. package/dist/frontend/program.d.ts +3 -2
  201. package/dist/frontend/program.js +495 -68
  202. package/dist/frontend/program.js.map +1 -1
  203. package/dist/frontend/resolve.d.ts +10 -2
  204. package/dist/frontend/resolve.js +90 -14
  205. package/dist/frontend/resolve.js.map +1 -1
  206. package/dist/frontend/resolve.test.js +4 -1
  207. package/dist/frontend/resolve.test.js.map +1 -1
  208. package/dist/frontend/runtime-resolve.d.ts +22 -0
  209. package/dist/frontend/runtime-resolve.js +101 -0
  210. package/dist/frontend/runtime-resolve.js.map +1 -0
  211. package/dist/frontend/runtime-resolve.test.d.ts +1 -0
  212. package/dist/frontend/runtime-resolve.test.js +18 -0
  213. package/dist/frontend/runtime-resolve.test.js.map +1 -0
  214. package/dist/frontend/ts7/adapter.js +12 -13
  215. package/dist/frontend/ts7/adapter.js.map +1 -1
  216. package/dist/frontend/ts7/ast.js.map +1 -1
  217. package/dist/frontend/ts7/census-check.js +3 -3
  218. package/dist/frontend/ts7/census-check.js.map +1 -1
  219. package/dist/frontend/ts7/checker.d.ts +3 -0
  220. package/dist/frontend/ts7/checker.js +12 -3
  221. package/dist/frontend/ts7/checker.js.map +1 -1
  222. package/dist/frontend/ts7/enums.js.map +1 -1
  223. package/dist/frontend/ts7/program-adapter.js +42 -3
  224. package/dist/frontend/ts7/program-adapter.js.map +1 -1
  225. package/dist/frontend/type-mapper.js +120 -7
  226. package/dist/frontend/type-mapper.js.map +1 -1
  227. package/dist/index.d.ts +3 -3
  228. package/dist/index.js +50 -28
  229. package/dist/index.js.map +1 -1
  230. package/dist/ir/analysis.js.map +1 -1
  231. package/dist/ir/ir.d.ts +125 -41
  232. package/dist/ir/ir.js +82 -31
  233. package/dist/ir/ir.js.map +1 -1
  234. package/dist/ir/serialize.d.ts +1 -1
  235. package/dist/ir/serialize.js +1 -1
  236. package/dist/ir/validate.js +127 -95
  237. package/dist/ir/validate.js.map +1 -1
  238. package/dist/library/cache-primitives.js.map +1 -1
  239. package/dist/library/cache-reset.test.js.map +1 -1
  240. package/dist/library/compiler-self-identity.js.map +1 -1
  241. package/dist/library/fence-eval.js +3 -1
  242. package/dist/library/fence-eval.js.map +1 -1
  243. package/dist/library/int-infer.js +12 -1
  244. package/dist/library/int-infer.js.map +1 -1
  245. package/dist/library/int-infer.test.js +4 -4
  246. package/dist/library/int-infer.test.js.map +1 -1
  247. package/dist/library/library-cache.test.js +5 -5
  248. package/dist/library/library-profile.js.map +1 -1
  249. package/dist/library/sidecar-validate.js.map +1 -1
  250. package/dist/library/sidecar.js.map +1 -1
  251. package/dist/startup-cache.d.ts +9 -1
  252. package/dist/startup-cache.js +16 -1
  253. package/dist/startup-cache.js.map +1 -1
  254. package/dist/wasi-paths.js.map +1 -1
  255. package/package.json +18 -5
  256. package/surface-manifest.json +64 -8
@@ -1,10 +1,12 @@
1
1
  import { InternalCompilerError } from "../../errors.js";
2
2
  /* Statement lowering: the statement dispatch (lowerStmt), variable
3
3
  * declarations including destructuring patterns, scoped blocks, control
4
- * flow (if/while/for/for-of/do, switch, try/catch, jumps with the
5
- * finally-crossing fence), and blocked-binding poisoning. */
4
+ * flow (if/while/for/for-of/do, switch, try/catch, abrupt completions),
5
+ * explicit resource management, and blocked-binding poisoning. */
6
6
  import * as ts from "../ts7/adapter.js";
7
- import { lowerForOfGenerator, lowerYieldStarStatement } from "./lower-generators.js";
7
+ import { arrayValueRead, arrayValueStore, arrayValueType } from "./array-values.js";
8
+ import { lowerForAwaitGenerator, lowerForOfGenerator, lowerYieldStarStatement } from "./lower-generators.js";
9
+ import { lowerForAwaitBuiltin } from "./lower-async-iteration.js";
8
10
  import { BOOL, BYTES_U8, CAUGHT, DYN, F64, JSVAL, STRING, UNDEFINED_T, VOID, arrayOf, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/ir.js";
9
11
  import { PoisonError, boundIdentifiersOf, dynFallbackType, dynUndefinedExpr, importCallHandleType, neverTaintedJsType, stmtUsesIsland, uncheckedOverloadHandleCall } from "./lowerer.js";
10
12
  import { enforceLibBoundary } from "./lib-boundary.js";
@@ -13,21 +15,263 @@ import { COMPOUND_ASSIGN_OPS, STR_METHODS, UNSUPPORTED_STMT, isStdlibMember, sid
13
15
  import { isProvenanceSourceFile } from "../provenance-registry.js";
14
16
  import { ambientUndefVarRootOf, lowerImportEquals, nsUndefRead, nsWritableTarget, trapDeclRootOf } from "./lower-namespaces.js";
15
17
  import { expandoWritableTarget, lowerExpandoAssignStmt } from "./lower-expando.js";
16
- import { lowerForOfArrayIter, lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, objectIterOverIndexShape, strCharsCall } from "./lower-containers.js";
17
- import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, deadUnmappableBinding, implicitLocalFnInfoOf, implicitLocalFnNodeOf, nullishExprUnitOf, nullishGenericBindingUnitOf, recordKeysArrayCall } from "./lower-calls.js";
18
+ import { lowerForOfArrayIter, lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, lowerSafeIndexRead, objectIterOverIndexShape, strCharsCall } from "./lower-containers.js";
19
+ import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, deadUnmappableBinding, implicitLocalFnInfoOf, implicitLocalFnNodeOf, implicitMethodCallInfersReturn, nullishExprUnitOf, nullishGenericBindingUnitOf, recordKeysArrayCall } from "./lower-calls.js";
18
20
  import { isMixinFnBinding, mixinResultBindingClassOf } from "./lower-mixins.js";
19
21
  import { genericIfaceBindingKeepsClass } from "./lower-classes.js";
20
- import { lowerStreamUnderscoreAssign, streamClassAliasDecl, streamSidesOf } from "./lower-stream.js";
22
+ import { lowerStreamUnderscoreAssign, streamClassAliasDecl } from "./lower-stream.js";
21
23
  import { lowerHttpResPropertyAssignment, lowerHttpServerTimeoutAssignment, lowerServerCloseOverrideAssignment } from "./lower-server.js";
22
24
  import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf, createRequireBindingDecl, createRequireCalleeFileOf, createRequireNamespaceDecl, textCodecBindingDecl } from "./lower-builtins.js";
23
25
  import { lowerEnumDeclaration } from "./lower-enums.js";
24
- import { abstractPropertyDeclOf, aliasTypeofNarrows, isMatchSliceType, lowerAbsenceProbe, lowerGroupsProjection, matchResultNamedGroupsOf, probeLower, pureReemittable, runtimeOptionalTrueIds, symbolFieldInfo, withRuntimeOptionalNarrowed } from "./lower-exprs.js";
26
+ import { abstractPropertyDeclOf, aliasTypeofNarrows, isMatchSliceType, lowerAbsenceProbe, lowerGroupsProjection, lowerOptionalNumber, matchResultNamedGroupsOf, runtimeOptionalTrueIds, symbolFieldInfo, withRuntimeOptionalNarrowed } from "./lower-exprs.js";
27
+ import { isSafeToRepeat } from "./expressions/evaluation-safety.js";
28
+ import { tryLowerExpression } from "./expressions/try-lower-expression.js";
25
29
  import { checkerPanicDiag, isCheckerPanic, requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
26
30
  import { isParseArgsDynTypeName, isUnitOnlyTsType, unitOnlyUnion } from "../type-mapper.js";
27
31
  import { canonicalBuiltinModule } from "../builtin-modules.js";
28
32
  import { isRelativeSpecifier } from "../workspace-registry.js";
29
33
  import { probeNodeRequireRefusal } from "../npm.js";
30
- import { varRef } from "../../ir/build.js";
34
+ import { numLit, varRef } from "../../ir/build.js";
35
+ import { constituentTypes } from "../ts7/checker.js";
36
+ import { npmStaticPackageOfPath } from "../npm-static.js";
37
+ import { fenceNodeModuleMutation } from "./lower-node-module.js";
38
+ /** An opted package's directly-returned empty array may take the function's
39
+ * explicit static return element type. This is the common JS accumulator
40
+ * shape (`const result = []; result.push(value); return result`) and is
41
+ * narrower than treating every evolving array in the function alike. */
42
+ function npmStaticReturnedEmptyArrayType(lowerer, decl) {
43
+ if (!ts.isIdentifier(decl.name) ||
44
+ decl.initializer === undefined ||
45
+ !ts.isArrayLiteralExpression(decl.initializer) ||
46
+ decl.initializer.elements.length !== 0 ||
47
+ !isJsSourceFile(decl.getSourceFile()) ||
48
+ npmStaticPackageOfPath(decl.getSourceFile().fileName) === null ||
49
+ lowerer.ctx.returnType.kind !== "array") {
50
+ return null;
51
+ }
52
+ const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
53
+ if (symbol === undefined)
54
+ return null;
55
+ let owner = decl.parent;
56
+ while (owner !== undefined && !ts.isFunctionLike(owner))
57
+ owner = owner.parent;
58
+ const ownerBody = owner !== undefined && "body" in owner ? owner.body : undefined;
59
+ if (ownerBody === undefined || !ts.isBlock(ownerBody))
60
+ return null;
61
+ let returned = false;
62
+ ts.walkPreorder(ownerBody, (node) => {
63
+ if (node !== ownerBody && ts.isFunctionLike(node))
64
+ return "skip";
65
+ if (!ts.isReturnStatement(node) || node.expression === undefined)
66
+ return undefined;
67
+ let expression = node.expression;
68
+ while (ts.isParenthesizedExpression(expression))
69
+ expression = expression.expression;
70
+ if (ts.isIdentifier(expression) && lowerer.checker.getSymbolAtLocation(expression) === symbol) {
71
+ returned = true;
72
+ return "stop";
73
+ }
74
+ return undefined;
75
+ });
76
+ return returned ? lowerer.ctx.returnType : null;
77
+ }
78
+ /** A JS loop cursor in opted package source can be initialized with `T`
79
+ * and later assigned a nullable self link (`cursor = cursor.parent`). Keep
80
+ * that optional value in the local so the next loop condition, rather than
81
+ * the assignment, narrows it. */
82
+ function npmStaticOptionalSelfWriteType(lowerer, decl, initial) {
83
+ if (!ts.isIdentifier(decl.name) ||
84
+ !isJsSourceFile(decl.getSourceFile()) ||
85
+ npmStaticPackageOfPath(decl.getSourceFile().fileName) === null ||
86
+ initial.kind === "union") {
87
+ return null;
88
+ }
89
+ const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
90
+ if (symbol === undefined)
91
+ return null;
92
+ let owner = decl.parent;
93
+ while (owner !== undefined && !ts.isFunctionLike(owner))
94
+ owner = owner.parent;
95
+ const ownerBody = owner !== undefined && "body" in owner ? owner.body : undefined;
96
+ if (ownerBody === undefined || !ts.isBlock(ownerBody))
97
+ return null;
98
+ let candidate = null;
99
+ let invalid = false;
100
+ ts.walkPreorder(ownerBody, (node) => {
101
+ if (node !== ownerBody && ts.isFunctionLike(node))
102
+ return "skip";
103
+ if (ts.isBinaryExpression(node)) {
104
+ let left = node.left;
105
+ while (ts.isParenthesizedExpression(left))
106
+ left = left.expression;
107
+ if (ts.isIdentifier(left) && lowerer.checker.getSymbolAtLocation(left) === symbol) {
108
+ if (node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) {
109
+ invalid = true;
110
+ return "stop";
111
+ }
112
+ const written = lowerer.mapTypeOf(lowerer.typeOf(node.right));
113
+ if (written !== null && typeEquals(written, initial))
114
+ return undefined;
115
+ const arms = written?.kind === "union" ? lowerer.unions.get(written.unionId)?.arms : undefined;
116
+ if (written?.kind !== "union" ||
117
+ arms === undefined ||
118
+ !arms.some((arm) => typeEquals(arm, initial)) ||
119
+ !arms.every((arm) => typeEquals(arm, initial) || isUnitType(arm)) ||
120
+ (candidate !== null && !typeEquals(candidate, written))) {
121
+ invalid = true;
122
+ return "stop";
123
+ }
124
+ candidate = written;
125
+ }
126
+ }
127
+ if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
128
+ ts.isIdentifier(node.operand) &&
129
+ lowerer.checker.getSymbolAtLocation(node.operand) === symbol) {
130
+ invalid = true;
131
+ return "stop";
132
+ }
133
+ return undefined;
134
+ });
135
+ return invalid ? null : candidate;
136
+ }
137
+ function isAwaitUsing(list) {
138
+ return (list.flags & ts.NodeFlags.AwaitUsing) === ts.NodeFlags.AwaitUsing;
139
+ }
140
+ function stdlibTypeNamed(lowerer, node, name) {
141
+ const visit = (t) => {
142
+ if (t.flags & ts.TypeFlags.Union)
143
+ return constituentTypes(t).some(visit);
144
+ const sym = t.getAliasSymbol() ?? t.getSymbol();
145
+ return sym?.name === name && lowerer.checker.declarationsOf(sym).some((d) => lowerer.isStdlibFile(d.getSourceFile()));
146
+ };
147
+ return visit(lowerer.typeOf(node));
148
+ }
149
+ function cleanupCall(lowerer, node, value, awaited) {
150
+ const loc = locOf(node);
151
+ if (value.type.kind === "object") {
152
+ const info = lowerer.classes.get(value.type.className);
153
+ if (!info)
154
+ lowerer.flushDeferredClass(value.type.className);
155
+ const asyncFound = awaited && info ? lowerer.findMethodOn(info, "sym:asyncDispose") : null;
156
+ const syncFound = info ? lowerer.findMethodOn(info, "sym:dispose") : null;
157
+ const member = asyncFound ? "sym:asyncDispose" : syncFound ? "sym:dispose" : null;
158
+ const found = asyncFound ?? syncFound;
159
+ if (!info || !member || !found)
160
+ return null;
161
+ if (found.sig.params.length !== 0) {
162
+ lowerer.unsupported("SC1090", node, `${member === "sym:asyncDispose" ? "[Symbol.asyncDispose]" : "[Symbol.dispose]"} methods with parameters`);
163
+ }
164
+ return {
165
+ call: lowerer.accessorCall(value.type.className, member, value, [], found.sig.ret, loc),
166
+ async: member === "sym:asyncDispose",
167
+ };
168
+ }
169
+ if (value.type.kind === "fileHandle") {
170
+ if (!awaited)
171
+ lowerer.unsupported("SC1090", node, "synchronous 'using' with an async-only FileHandle (use 'await using')");
172
+ return {
173
+ call: { kind: "libCall", fn: "fileHandle.close", args: [value], type: { kind: "promise", inner: VOID }, loc },
174
+ async: true,
175
+ };
176
+ }
177
+ if (value.type.kind === "child") {
178
+ return {
179
+ call: {
180
+ kind: "libCall",
181
+ fn: "child.kill",
182
+ args: [value, { kind: "strLit", value: "SIGTERM", type: STRING, loc }],
183
+ type: BOOL,
184
+ loc,
185
+ },
186
+ async: false,
187
+ };
188
+ }
189
+ if (value.type.kind === "f64") {
190
+ if (stdlibTypeNamed(lowerer, node, "Timeout")) {
191
+ return { call: { kind: "libCall", fn: "timers.clearTimeout", args: [value], type: VOID, loc }, async: false };
192
+ }
193
+ if (stdlibTypeNamed(lowerer, node, "Immediate")) {
194
+ return { call: { kind: "libCall", fn: "timers.clearImmediate", args: [value], type: VOID, loc }, async: false };
195
+ }
196
+ if (stdlibTypeNamed(lowerer, node, "Interface")) {
197
+ return { call: { kind: "libCall", fn: "rl.close", args: [value], type: VOID, loc }, async: false };
198
+ }
199
+ }
200
+ return null;
201
+ }
202
+ function cleanupForValue(lowerer, node, value, awaited) {
203
+ const loc = locOf(node);
204
+ if (isUnitType(value.type))
205
+ return [];
206
+ if (value.type.kind === "union") {
207
+ const def = lowerer.unions.get(value.type.unionId);
208
+ if (!def)
209
+ throw new InternalCompilerError(`lowerer bug: unknown using union ${value.type.unionId}`);
210
+ const out = [];
211
+ for (let tag = 0; tag < def.arms.length; tag++) {
212
+ const arm = def.arms[tag];
213
+ if (isUnitType(arm))
214
+ continue;
215
+ const narrowed = { kind: "unionNarrow", unionId: value.type.unionId, tag, value, type: arm, loc };
216
+ const body = cleanupForValue(lowerer, node, narrowed, awaited);
217
+ out.push({
218
+ kind: "if",
219
+ cond: { kind: "unionIsTag", unionId: value.type.unionId, tag, negated: false, value, type: BOOL, loc },
220
+ then: body,
221
+ else_: null,
222
+ loc,
223
+ });
224
+ }
225
+ return out;
226
+ }
227
+ const cleanup = cleanupCall(lowerer, node, value, awaited);
228
+ if (!cleanup) {
229
+ lowerer.unsupported("SC1090", node, `disposing values of type '${lowerer.fmt(value.type)}' (use a class with a zero-parameter [Symbol.dispose] or [Symbol.asyncDispose] method, or a supported Node resource)`);
230
+ }
231
+ const call = cleanup.call;
232
+ if (cleanup.async) {
233
+ if (call.type.kind !== "promise" || call.type.inner.kind !== "void") {
234
+ lowerer.unsupported("SC1090", node, "[Symbol.asyncDispose] methods whose result is not Promise<void>");
235
+ }
236
+ return [{ kind: "exprStmt", expr: { kind: "awaitExpr", value: call, type: VOID, loc }, loc }];
237
+ }
238
+ const out = [{ kind: "exprStmt", expr: call, loc }];
239
+ if (awaited)
240
+ out.push({ kind: "exprStmt", expr: { kind: "libCall", fn: "async.hop", args: [], type: VOID, loc }, loc });
241
+ return out;
242
+ }
243
+ function lowerUsingStatement(lowerer, stmt) {
244
+ if (ts.isSourceFile(stmt.parent)) {
245
+ lowerer.unsupported("SC1090", stmt, "top-level 'using' declarations (wrap the resource lifetime in a block or function)");
246
+ }
247
+ if (ts.isCaseClause(stmt.parent) || ts.isDefaultClause(stmt.parent)) {
248
+ lowerer.unsupported("SC1090", stmt, "'using' declarations directly in switch clauses (wrap the clause body in a block)");
249
+ }
250
+ const awaited = isAwaitUsing(stmt.declarationList);
251
+ if (awaited && !lowerer.ctx.isAsync) {
252
+ lowerer.unsupported("SC1090", stmt, "'await using' outside an async function or top-level-await module");
253
+ }
254
+ const registrations = [];
255
+ for (const decl of stmt.declarationList.declarations) {
256
+ if (!ts.isIdentifier(decl.name))
257
+ lowerer.unsupported("SC1031", decl.name);
258
+ if (decl.initializer === undefined)
259
+ lowerer.unsupported("SC1090", decl, "a 'using' declaration without an initializer");
260
+ const acquire = lowerer.lowerVarDecl(decl, false);
261
+ if (!acquire)
262
+ throw new InternalCompilerError("lowerer bug: using declaration emitted no acquisition");
263
+ const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
264
+ if (!symbol)
265
+ throw new InternalCompilerError("lowerer bug: using declaration has no symbol");
266
+ const binding = lowerer.bindingIn(lowerer.ctx, symbol) ?? lowerer.globalsBySymbol.get(symbol) ?? null;
267
+ if (!binding)
268
+ throw new InternalCompilerError("lowerer bug: using declaration has no runtime binding");
269
+ const value = varRef(binding.id, binding.type, locOf(decl.name));
270
+ const cleanup = cleanupForValue(lowerer, decl.name, value, awaited);
271
+ registrations.push({ acquire, cleanup, loc: locOf(decl) });
272
+ }
273
+ return registrations;
274
+ }
31
275
  /** `const X = /* @__PURE__ *\/ makeX()` at a module's top level: every
32
276
  * declarator initialized by a call (or `new`) carrying the bundler PURE
33
277
  * annotation in its leading trivia — the shape --provenance-sources may
@@ -112,6 +356,7 @@ export function provenanceElidedConstDecl(lowerer, decl) {
112
356
  * `let a = 1, b = a + 1;`) but is counted once. */
113
357
  export function lowerStmts(lowerer, stmts) {
114
358
  const out = [];
359
+ const usingMarkers = [];
115
360
  // JAVASCRIPT sources defer their compile fences to runtime (the
116
361
  // JS-input design: no annotations exist to change what lowers, so a
117
362
  // statement whose construct has no static lowering compiles to a
@@ -143,7 +388,25 @@ export function lowerStmts(lowerer, stmts) {
143
388
  }
144
389
  const diagsBefore = lowerer.diags.length;
145
390
  try {
146
- const lowered = lowerer.lowerStmt(stmt);
391
+ let lowered;
392
+ let appended = false;
393
+ if (ts.isVariableStatement(stmt) &&
394
+ (stmt.declarationList.flags & ts.NodeFlags.Using) !== 0) {
395
+ const registrations = lowerUsingStatement(lowerer, stmt);
396
+ const accounting = [];
397
+ for (const registration of registrations) {
398
+ out.push(registration.acquire);
399
+ accounting.push(registration.acquire, ...registration.cleanup);
400
+ if (registration.cleanup.length > 0) {
401
+ usingMarkers.push({ start: out.length, cleanup: registration.cleanup, loc: registration.loc });
402
+ }
403
+ }
404
+ lowered = accounting;
405
+ appended = true;
406
+ }
407
+ else {
408
+ lowered = lowerer.lowerStmt(stmt);
409
+ }
147
410
  // The lib-boundary chokepoint (lib-boundary.ts): checked-dynamic
148
411
  // arguments that reached builtin-call slots without the checked
149
412
  // coercion get their dynCheck here, and the uncoercible ones
@@ -152,10 +415,12 @@ export function lowerStmts(lowerer, stmts) {
152
415
  // deferred rejection.
153
416
  if (lowered)
154
417
  enforceLibBoundary(lowerer, lowered);
155
- if (Array.isArray(lowered))
156
- out.push(...lowered);
157
- else if (lowered)
158
- out.push(lowered);
418
+ if (!appended) {
419
+ if (Array.isArray(lowered))
420
+ out.push(...lowered);
421
+ else if (lowered)
422
+ out.push(lowered);
423
+ }
159
424
  // Island accounting (--dynamic coverage): a statement that lowered
160
425
  // but carries island constructs compiles DYNAMICALLY — its work
161
426
  // runs in the embedded engine, and the report says so instead of
@@ -214,6 +479,23 @@ export function lowerStmts(lowerer, stmts) {
214
479
  }
215
480
  }
216
481
  }
482
+ // Each acquisition protects the remainder of its lexical statement
483
+ // list. Folding from the last registration outward produces the
484
+ // spec's LIFO nesting and ensures a later initializer failure only
485
+ // disposes resources acquired before it.
486
+ for (let i = usingMarkers.length - 1; i >= 0; i--) {
487
+ const marker = usingMarkers[i];
488
+ const body = out.splice(marker.start);
489
+ out.push({
490
+ kind: "tryCatch",
491
+ tryBody: body,
492
+ catchBody: null,
493
+ catchLocalId: null,
494
+ finallyBody: marker.cleanup,
495
+ suppressFinallyErrors: true,
496
+ loc: marker.loc,
497
+ });
498
+ }
217
499
  }
218
500
  finally {
219
501
  lowerer.activeStmtLists.pop();
@@ -588,44 +870,6 @@ export function lowerScopedBlock(lowerer, stmt) {
588
870
  lowerer.scopes.pop();
589
871
  }
590
872
  }
591
- /** The finally fences. `return` crossing OUT of a try/catch body guarded
592
- * by a finally is SUPPORTED now (the backend's pending-return path runs
593
- * every crossed finally inner-to-outer before the function returns, the
594
- * value snapshotted first — Node-exact); what stays rejected is (a) any
595
- * jump out of a finally BLOCK itself (a return there would REPLACE a
596
- * pending completion — clearing a still-propagating exception or
597
- * abandoning a pending return, a model the emitter doesn't implement),
598
- * and (b) break/continue crossing a try-with-finally (their targets bind
599
- * inside the function; the pending-action plumbing exists only for
600
- * return, the shape real code needs). Rejected, not miscompiled. Plain
601
- * try/catch is transparent to jumps (it never appears in ctl). A LABELED
602
- * jump walks outward past every construct (labeled targets can be
603
- * arbitrarily far out) until the entry carrying its label, applying the
604
- * same finally fences to everything crossed on the way. */
605
- export function rejectJumpCrossingFinally(lowerer, kw, stmt, label) {
606
- const ctl = lowerer.ctx.ctl;
607
- for (let i = ctl.length - 1; i >= 0; i--) {
608
- const c = ctl[i];
609
- if (c.kind === "finallyBlock") {
610
- lowerer.unsupported("SC1090", stmt, `'${kw}' out of a 'finally' block (it would replace the pending completion — restructure so the finally only cleans up)`);
611
- }
612
- if (c.kind === "tryFinally") {
613
- if (kw !== "return") {
614
- lowerer.unsupported("SC1090", stmt, `'${kw}' crossing a 'finally' block`);
615
- }
616
- continue; // return runs the finally on the way out — supported
617
- }
618
- if (kw === "return")
619
- continue; // return crosses every construct
620
- if (label !== undefined) {
621
- if (c.labels?.includes(label))
622
- return; // the labeled target — binds here
623
- continue; // an inner construct the labeled jump exits
624
- }
625
- if (c.kind === "loop" || (kw === "break" && c.kind === "switch"))
626
- return; // binds inside the region
627
- }
628
- }
629
873
  /** `lbl: stmt` — labeled statements. A label chain collapses to one name
630
874
  * list (`a: b: while ...` — both names target the same loop, JS-exact).
631
875
  * Loops and switches take the labels DIRECTLY (a labeled continue needs
@@ -719,7 +963,6 @@ export function lowerStmt(lowerer, stmt) {
719
963
  if (ts.isLabeledStatement(stmt))
720
964
  return lowerLabeled(lowerer, stmt);
721
965
  if (ts.isReturnStatement(stmt)) {
722
- lowerer.rejectJumpCrossingFinally("return", stmt);
723
966
  // Implicit-any instance RETURN INFERENCE (lower-calls'
724
967
  // resolveInferredReturn): the value lowers BARE and the statement
725
968
  // records itself — the post-pass settles the instance's return type
@@ -783,10 +1026,8 @@ export function lowerStmt(lowerer, stmt) {
783
1026
  if (!target || (kw === "continue" && target.kind !== "loop")) {
784
1027
  lowerer.unsupported("SC1050", stmt, `'${kw} ${label}' targeting this statement form (the labeled statement lowers through a desugar that has no label point)`);
785
1028
  }
786
- lowerer.rejectJumpCrossingFinally(kw, stmt, label);
787
1029
  return { kind: kw, label, loc: locOf(stmt) };
788
1030
  }
789
- lowerer.rejectJumpCrossingFinally(kw, stmt);
790
1031
  // tsc rejects break outside loops/switches and continue outside loops,
791
1032
  // so the context is always valid here (the validator re-checks).
792
1033
  return { kind: kw, loc: locOf(stmt) };
@@ -1264,6 +1505,11 @@ export function lowerDestructuringDecl(lowerer, decl, isLet) {
1264
1505
  /** One pattern level: emits a declaration (or global assignment) per
1265
1506
  * bound name, recursing through hidden temps for nested patterns. */
1266
1507
  export function lowerBindingPattern(lowerer, pattern, srcRef, srcType, isLet, out, dynSpell, allowDynObject = false) {
1508
+ const present = patternSourceValue(lowerer, srcRef(), ts.isArrayBindingPattern(pattern), out, locOf(pattern), dynSpell);
1509
+ if (!typeEquals(present.type, srcType)) {
1510
+ lowerer.lowerBindingPattern(pattern, () => present, present.type, isLet, out, dynSpell, allowDynObject);
1511
+ return;
1512
+ }
1267
1513
  if (ts.isArrayBindingPattern(pattern)) {
1268
1514
  lowerer.fenceStaticHeadersIteration(pattern);
1269
1515
  // Tuple sources: each position is a field read of the tuple's record
@@ -1500,29 +1746,22 @@ export function lowerBindingPattern(lowerer, pattern, srcRef, srcType, isLet, ou
1500
1746
  if (ts.isOmittedExpression(el) || el.name === undefined)
1501
1747
  return; // hole: position skipped, like JS over an array
1502
1748
  const loc = locOf(el);
1503
- // `[current, ...rest]` over an ARRAY source: the rest binds a
1504
- // fresh tail copy — `arr.slice(i)`, exactly the JS surplus packing
1505
- // (tsc pins rest to the last position; an empty tail is `[]`) —
1506
- // and a NESTED rest pattern (`[...[a, b]]`) destructures the copy.
1749
+ // Array rest drains values from the iterator, so holes become
1750
+ // present undefined in the fresh tail. Nested rest patterns then
1751
+ // destructure that materialized copy.
1507
1752
  if (el.dotDotDotToken) {
1508
1753
  if (el.initializer) {
1509
1754
  lowerer.unsupported("SC1031", el, "defaults on rest elements"); // tsc rejects; defensive
1510
1755
  }
1511
- const value = {
1512
- kind: "arrIntrinsic",
1513
- method: "slice",
1514
- receiver: srcRef(),
1515
- args: [{ kind: "numLit", value: i, type: F64, loc }],
1516
- type: srcType,
1517
- loc,
1518
- };
1756
+ const value = arrayRestValue(srcRef(), i, srcType, loc);
1519
1757
  lowerer.bindPatternTarget(el.name, value, isLet, out);
1520
1758
  return;
1521
1759
  }
1522
- let value = {
1760
+ const position = { kind: "numLit", value: i, type: F64, loc };
1761
+ let value = lowerSafeIndexRead(lowerer, srcRef(), position, loc) ?? {
1523
1762
  kind: "arrayGet",
1524
1763
  arr: srcRef(),
1525
- index: { kind: "numLit", value: i, type: F64, loc },
1764
+ index: position,
1526
1765
  type: srcType.elem,
1527
1766
  loc,
1528
1767
  };
@@ -1842,6 +2081,33 @@ export function lowerBindingPattern(lowerer, pattern, srcRef, srcType, isLet, ou
1842
2081
  lowerer.bindPatternTarget(el.name, value, isLet, out);
1843
2082
  }
1844
2083
  }
2084
+ /** Destructuring a missing array element throws before reading members.
2085
+ * Only unwrap a union after testing every nullish arm; this also keeps
2086
+ * dense record/tuple callbacks on their existing payload representation. */
2087
+ function patternSourceValue(lowerer, value, arrayPattern, out, loc, spelling) {
2088
+ if (value.type.kind !== "union")
2089
+ return value;
2090
+ const unionId = value.type.unionId;
2091
+ const arms = lowerer.unions.get(unionId)?.arms;
2092
+ const present = arms?.filter((arm) => !isUnitType(arm));
2093
+ if (!arms || present?.length !== 1 || arms.length === 1)
2094
+ return value;
2095
+ for (const [tag, arm] of arms.entries()) {
2096
+ if (!isUnitType(arm))
2097
+ continue;
2098
+ const unit = arm.kind === "undefinedT" ? "undefined" : "null";
2099
+ const message = arrayPattern
2100
+ ? spelling ?? `${unit} is not iterable (cannot read property Symbol(Symbol.iterator))`
2101
+ : `Cannot destructure ${unit}`;
2102
+ out.push({
2103
+ kind: "if",
2104
+ cond: { kind: "unionIsTag", unionId, tag, value, negated: false, type: BOOL, loc },
2105
+ then: [{ kind: "throw", value: { kind: "libCall", fn: "error.new", args: [{ kind: "strLit", value: message, type: STRING, loc }], type: { kind: "object", className: "%TypeError" }, loc }, loc }],
2106
+ else_: null, loc,
2107
+ });
2108
+ }
2109
+ return { kind: "unionNarrow", unionId, tag: lowerer.armTag(unionId, present[0]), value, type: present[0], loc };
2110
+ }
1845
2111
  /** The type a pattern element binds at — the checker's type of the bound
1846
2112
  * identifier, or of the nested pattern itself (its implied type). Null
1847
2113
  * when the type has no static mapping (the caller fences). */
@@ -2138,13 +2404,9 @@ function undefArmDefault(lowerer, blame, init, value, bodyT) {
2138
2404
  const dflt = lowerer.lowerExprExpecting(init, bodyT);
2139
2405
  return { kind: "ternary", cond: isUndef, then: dflt, else_: present, type: bodyT, loc };
2140
2406
  }
2141
- /** `[a = dflt]` over an ARRAY source: the default fires when the position
2142
- * is past the end OR holds undefined (JS's one rule — both spell the
2143
- * read as undefined there), so the defaulted read carries a bounds test
2144
- * where the plain read would trap (the array divergence's carve-out).
2145
- * Elements with an undefined arm land in a hidden temp first (in-bounds
2146
- * undefined and past-the-end collapse to the same arm), then the
2147
- * ordinary undefined-arm default applies. */
2407
+ /** An array binding default tests the value produced by the read. Holes,
2408
+ * out-of-range positions, and explicit undefined share the same arm.
2409
+ * Typed-array reads retain the separate bounds check below. */
2148
2410
  function arrayPositionDefault(lowerer, el, read, srcRef, index, elemT, out) {
2149
2411
  const name = el.name; // callers skip nameless elisions
2150
2412
  const bodyT = patternBindingType(lowerer, name);
@@ -2157,6 +2419,11 @@ function arrayPositionDefault(lowerer, el, read, srcRef, index, elemT, out) {
2157
2419
  * arrives from the existing binding instead of the checker. */
2158
2420
  function arrayPositionDefaultValue(lowerer, blame, init, read, srcRef, index, elemT, bodyT, out) {
2159
2421
  const loc = read.loc;
2422
+ if (read.type.kind === "union" && lowerer.armTag(read.type.unionId, UNDEFINED_T) >= 0) {
2423
+ const tmp = lowerer.declareHiddenLocal("%delem", read.type);
2424
+ out.push({ kind: "varDecl", localId: tmp.id, init: read, loc });
2425
+ return undefArmDefault(lowerer, blame, init, varRef(tmp.id, read.type, loc), bodyT);
2426
+ }
2160
2427
  // The unit-only element (a declared-empty-tuple source riding the
2161
2428
  // array representation): every read is undefined — the default IS the
2162
2429
  // binding.
@@ -2213,6 +2480,15 @@ function arrayPositionDefaultValue(lowerer, blame, init, read, srcRef, index, el
2213
2480
  loc,
2214
2481
  };
2215
2482
  }
2483
+ /** Array rest copies values and materializes holes. Removing the prefix
2484
+ * through the copying splice primitive preserves the payload ABI. */
2485
+ function arrayRestValue(receiver, from, type, loc) {
2486
+ return {
2487
+ kind: "arrIntrinsic", method: "toSpliced", receiver,
2488
+ args: [numLit(0, loc), numLit(from, loc), { kind: "arrayLit", elems: [], type, loc }],
2489
+ type, loc,
2490
+ };
2491
+ }
2216
2492
  /** The fresh tail for `[head, ...rest]` over a TUPLE source, packed under
2217
2493
  * the checker's own rest type: an ARRAY of the remaining positions (each
2218
2494
  * read coercing into the element type) or a tuple RECORD when the
@@ -2790,12 +3066,31 @@ export function bindPatternTarget(lowerer, name, value, isLet, out, allowDynObje
2790
3066
  const mapped = lowerer.mapTypeOf(lowerer.typeOf(name));
2791
3067
  return mapped === null || (lowerer.dynamic && jsvalFlavoredType(mapped));
2792
3068
  })();
2793
- const type = dynStays ? DYN : lowerer.irTypeOf(name);
3069
+ let type = dynStays ? DYN : lowerer.runtimeOptionalBindingType(name, lowerer.irTypeOf(name)) ?? lowerer.irTypeOf(name);
2794
3070
  if (type.kind === "void")
2795
3071
  lowerer.badType(name, lowerer.typeOf(name));
3072
+ // Array destructuring positions are ordinary indexed reads. When the
3073
+ // checker was built without noUncheckedIndexedAccess it still spells
3074
+ // the binding as the bare element type, but the runtime value carries
3075
+ // the same `T | undefined` possibility as a direct array read. Adopt
3076
+ // that union for an unannotated pattern binding so undefined survives
3077
+ // through later locals, templates, operators, and narrowing instead
3078
+ // of being extracted with a checked arm read.
3079
+ let runtimeOptional = false;
3080
+ if (!dynStays && value.type.kind === "union" &&
3081
+ lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0 &&
3082
+ typeEquals(lowerer.stripUndefinedArm(value.type), type)) {
3083
+ type = value.type;
3084
+ runtimeOptional = true;
3085
+ }
2796
3086
  const coerced = lowerer.coerceInto(name, value, type);
2797
3087
  const local = lowerer.declareLocal(name, name.text, type, isLet);
2798
3088
  out.push({ kind: "varDecl", localId: local.id, init: coerced, loc });
3089
+ if (runtimeOptional) {
3090
+ const runtimeOptionalRoot = lowerer.runtimeOptionalRootOf(local);
3091
+ lowerer.runtimeOptionalLocals.add(runtimeOptionalRoot);
3092
+ lowerer.runtimeOptionalStorageLocals.add(runtimeOptionalRoot);
3093
+ }
2799
3094
  return;
2800
3095
  }
2801
3096
  const tmp = lowerer.declareHiddenLocal("%destr", value.type);
@@ -2849,6 +3144,95 @@ export function lowerVarDeclList(lowerer, list) {
2849
3144
  throw new InternalCompilerError("lowerer bug: for-init declarator resolved to a global");
2850
3145
  return lowered;
2851
3146
  }
3147
+ /** A single static type for an initializer-less JavaScript `let` whose
3148
+ * later plain writes all agree. An evolving JS local reads as `undefined`
3149
+ * until control flow proves a write, so a scalar slot is sound only when
3150
+ * every ordinary use already has that concrete flow type. Any closure
3151
+ * capture stays checked-dynamic: it can observe the local before an outer
3152
+ * assignment, and the checker deliberately leaves captured evolving lets
3153
+ * broad. */
3154
+ function inferredEvolvingLetType(lowerer, decl) {
3155
+ if (!ts.isIdentifier(decl.name))
3156
+ return null;
3157
+ const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
3158
+ const scope = decl.parent.parent.parent;
3159
+ if (symbol === undefined || (!ts.isBlock(scope) && !ts.isSourceFile(scope)))
3160
+ return null;
3161
+ const isPlainAssignmentTarget = (node) => {
3162
+ let target = node;
3163
+ while (ts.isParenthesizedExpression(target.parent) && target.parent.expression === target) {
3164
+ target = target.parent;
3165
+ }
3166
+ const parent = target.parent;
3167
+ return ts.isBinaryExpression(parent) &&
3168
+ parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
3169
+ parent.left === target;
3170
+ };
3171
+ const captures = (fn) => {
3172
+ let captured = false;
3173
+ ts.walkPreorder(fn, (node) => {
3174
+ if (node !== fn &&
3175
+ ts.isIdentifier(node) &&
3176
+ lowerer.checker.getSymbolAtLocation(node) === symbol) {
3177
+ captured = true;
3178
+ return "stop";
3179
+ }
3180
+ return undefined;
3181
+ });
3182
+ return captured;
3183
+ };
3184
+ const writes = [];
3185
+ const reads = [];
3186
+ let invalid = false;
3187
+ ts.walkPreorder(scope, (node) => {
3188
+ if (node !== scope && ts.isFunctionLike(node)) {
3189
+ if (captures(node))
3190
+ invalid = true;
3191
+ return "skip";
3192
+ }
3193
+ if (ts.isBinaryExpression(node)) {
3194
+ let left = node.left;
3195
+ while (ts.isParenthesizedExpression(left))
3196
+ left = left.expression;
3197
+ if (ts.isIdentifier(left) && lowerer.checker.getSymbolAtLocation(left) === symbol) {
3198
+ if (node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) {
3199
+ invalid = true;
3200
+ return "stop";
3201
+ }
3202
+ if (ts.isCallExpression(node.right) && implicitMethodCallInfersReturn(lowerer, node.right)) {
3203
+ invalid = true;
3204
+ return "stop";
3205
+ }
3206
+ const mapped = lowerer.mapTypeOf(lowerer.typeOf(node.right));
3207
+ if (!mapped || mapped.kind === "dyn" || mapped.kind === "jsval" || mapped.kind === "void" || isUnitType(mapped)) {
3208
+ invalid = true;
3209
+ return "stop";
3210
+ }
3211
+ writes.push(mapped);
3212
+ }
3213
+ }
3214
+ if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
3215
+ ts.isIdentifier(node.operand) &&
3216
+ lowerer.checker.getSymbolAtLocation(node.operand) === symbol) {
3217
+ invalid = true;
3218
+ return "stop";
3219
+ }
3220
+ if (ts.isIdentifier(node) &&
3221
+ node !== decl.name &&
3222
+ lowerer.checker.getSymbolAtLocation(node) === symbol &&
3223
+ !isPlainAssignmentTarget(node)) {
3224
+ reads.push(node);
3225
+ }
3226
+ return undefined;
3227
+ });
3228
+ const first = writes[0];
3229
+ if (invalid || first === undefined || !writes.every((write) => typeEquals(write, first)))
3230
+ return null;
3231
+ return reads.every((read) => {
3232
+ const mapped = lowerer.mapTypeOf(lowerer.typeOf(read));
3233
+ return mapped !== null && typeEquals(mapped, first);
3234
+ }) ? first : null;
3235
+ }
2852
3236
  export function lowerVarDecl(lowerer, decl, isLet) {
2853
3237
  // --provenance-sources: an elided pure-annotated dead const emits
2854
3238
  // nothing (collectGlobals registered no global by the same test —
@@ -3026,8 +3410,23 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3026
3410
  // (statics are zero/NULL-initialized; uninitialized globals need no
3027
3411
  // statement at all).
3028
3412
  const declSymbol = lowerer.checker.getSymbolAtLocation(decl.name);
3029
- const g = declSymbol ? lowerer.globalsBySymbol.get(declSymbol) : undefined;
3413
+ const g = ts.isIdentifier(decl.name)
3414
+ ? lowerer.globalOf(decl.name)
3415
+ : (declSymbol ? lowerer.globalsBySymbol.get(declSymbol) : undefined);
3030
3416
  if (g) {
3417
+ let promotedGlobal = lowerer.runtimeOptionalBindingType(decl.name, g.type);
3418
+ if (g.type.kind === "func" && decl.initializer !== undefined && ts.isIdentifier(decl.initializer)) {
3419
+ const sig = lowerer.fnSigOf(decl.initializer);
3420
+ if (sig) {
3421
+ promotedGlobal = {
3422
+ ...g.type,
3423
+ params: sig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type),
3424
+ ret: sig.returnType,
3425
+ };
3426
+ }
3427
+ }
3428
+ if (!typeEquals(promotedGlobal, g.type))
3429
+ g.type = promotedGlobal;
3031
3430
  if (!decl.initializer) {
3032
3431
  // `let x: string | undefined;` at file scope: READABLE before any
3033
3432
  // assignment — JS gives undefined, so the slot must hold the
@@ -3048,7 +3447,16 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3048
3447
  return { kind: "assign", localId: g.id, value: wrapped, loc: locOf(decl) };
3049
3448
  return null;
3050
3449
  }
3051
- const init = lowerer.lowerExprExpecting(decl.initializer, g.type);
3450
+ const raw = lowerVariableInitializer(lowerer, decl.initializer);
3451
+ const arithmetic = raw.type.kind === "union"
3452
+ ? lowerer.unions.get(raw.type.unionId)?.arms
3453
+ : undefined;
3454
+ if (g.type.kind === "string" && arithmetic?.length === 2 &&
3455
+ arithmetic.some((a) => a.kind === "f64") && arithmetic.some((a) => a.kind === "string")) {
3456
+ g.type = raw.type;
3457
+ lowerer.runtimeOptionalArithmeticGlobals.add(g);
3458
+ }
3459
+ const init = lowerer.coerceInto(decl.initializer, raw, g.type);
3052
3460
  return { kind: "assign", localId: g.id, value: init, loc: locOf(decl) };
3053
3461
  }
3054
3462
  // A forward-captured const pre-declared as a TDZ box (an earlier
@@ -3084,6 +3492,9 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3084
3492
  // immediately (the type covers the unassigned state), so they
3085
3493
  // initialize to the interned undefined arm like an omitted optional.
3086
3494
  let type = lowerer.irTypeOf(decl.name);
3495
+ if (type.kind === "dyn" && isLet && isJsSourceFile(decl.getSourceFile())) {
3496
+ type = inferredEvolvingLetType(lowerer, decl) ?? type;
3497
+ }
3087
3498
  // `var v: void;` / `let x: undefined;` — a unit-only binding rides
3088
3499
  // the unit-only union (its undefined arm is the unassigned state,
3089
3500
  // which is also the only state).
@@ -3108,7 +3519,7 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3108
3519
  try {
3109
3520
  init = immediatelyGuardedAbsenceProbe(lowerer, decl)
3110
3521
  ? (lowerAbsenceProbe(lowerer, decl.initializer) ?? lowerer.lowerExpr(decl.initializer))
3111
- : lowerer.lowerExpr(decl.initializer);
3522
+ : lowerVariableInitializer(lowerer, decl.initializer);
3112
3523
  }
3113
3524
  catch (e) {
3114
3525
  if (e instanceof PoisonError) {
@@ -3154,6 +3565,12 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3154
3565
  (lowerer.dynamic && init.type.kind === "dyn" && jsvalFlavoredType(lowerer.mapTypeOf(lowerer.typeOf(decl.name)) ?? DYN) ? DYN : null) ??
3155
3566
  (bindingTainted ? null : lowerer.mapTypeOf(lowerer.typeOf(decl.name))) ??
3156
3567
  (init.type.kind === "dyn" ? DYN : null);
3568
+ if (lowerer.implicitParamTypes !== null &&
3569
+ init.type.kind === "dyn" &&
3570
+ isJsSourceFile(decl.getSourceFile()) &&
3571
+ npmStaticPackageOfPath(decl.getSourceFile().fileName) !== null) {
3572
+ type = DYN;
3573
+ }
3157
3574
  // A JS `let x = {}`: TS's empty-object-literal type admits ANY later
3158
3575
  // non-nullish assignment (`envs = {}`, later `envs =
3159
3576
  // Object.fromEntries(...)` — tsc accepts every such write, since
@@ -3166,6 +3583,11 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3166
3583
  if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple)
3167
3584
  type = DYN;
3168
3585
  }
3586
+ const returnedEmptyArrayType = npmStaticReturnedEmptyArrayType(lowerer, decl);
3587
+ if (returnedEmptyArrayType !== null) {
3588
+ type = returnedEmptyArrayType;
3589
+ init = lowerer.lowerExprExpecting(decl.initializer, returnedEmptyArrayType);
3590
+ }
3169
3591
  // A JS `const leaked = [];` (the evolving-array idiom — test/common's
3170
3592
  // leak ledger): tsc types the binding by its LATER pushes, but this
3171
3593
  // frontend answers the declaration name with the uninhabited never[],
@@ -3175,7 +3597,9 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3175
3597
  // checked-dynamic instead (the `let x = {}` stance, and lower-modules'
3176
3598
  // file-scope evolving-array rule): pushes ride the dyn array, typed
3177
3599
  // exits dynCheck.
3178
- if ((type === null || type.kind === "array") && isJsSourceFile(decl.getSourceFile())) {
3600
+ if (returnedEmptyArrayType === null &&
3601
+ (type === null || type.kind === "array") &&
3602
+ isJsSourceFile(decl.getSourceFile())) {
3179
3603
  let initExpr = decl.initializer;
3180
3604
  while (ts.isParenthesizedExpression(initExpr))
3181
3605
  initExpr = initExpr.expression;
@@ -3194,12 +3618,30 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3194
3618
  // and TypeScript's fresh-array probe idioms; an explicit annotation keeps
3195
3619
  // the caller's checked slot contract.
3196
3620
  let runtimeOptional = false;
3197
- if (!decl.type && type !== null && init.type.kind === "union" &&
3621
+ if (type !== null && init.type.kind === "union" &&
3622
+ lowerer.armTag(init.type.unionId, UNDEFINED_T) >= 0) {
3623
+ const present = lowerer.stripUndefinedArm(init.type);
3624
+ if (!typeEquals(present, type) && lowerer.widthLiftPlan(present, type) !== null) {
3625
+ const widened = type.kind === "union" ? lowerer.withUndefinedArmOf(type) : lowerer.withUndefinedArm(type);
3626
+ if (widened && lowerer.widthLiftPlan(init.type, widened) !== null) {
3627
+ type = widened;
3628
+ runtimeOptional = true;
3629
+ }
3630
+ }
3631
+ }
3632
+ if (type !== null && init.type.kind === "union" &&
3198
3633
  lowerer.armTag(init.type.unionId, UNDEFINED_T) >= 0 &&
3199
3634
  typeEquals(lowerer.stripUndefinedArm(init.type), type)) {
3200
3635
  type = init.type;
3201
3636
  runtimeOptional = true;
3202
3637
  }
3638
+ if (type?.kind === "array" && init.type.kind === "array" &&
3639
+ init.type.elem.kind === "union" &&
3640
+ lowerer.armTag(init.type.elem.unionId, UNDEFINED_T) >= 0 &&
3641
+ typeEquals(lowerer.stripUndefinedArm(init.type.elem), type.elem)) {
3642
+ type = init.type;
3643
+ runtimeOptional = true;
3644
+ }
3203
3645
  // A JS binding whose checker type spells a FUNCTION but whose VALUE is
3204
3646
  // already checked-dynamic (`const cb = mustCall(fn)` — the helper's
3205
3647
  // return rides its own box): keep the box. Extracting into the spelled
@@ -3219,14 +3661,15 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3219
3661
  // their tuple record likewise). Const only — an evolving-`any`
3220
3662
  // `let` may be reassigned a different shape later; genuine `any`
3221
3663
  // only — every other unmappable keeps its own diagnostic.
3222
- if (type === null && !isLet &&
3223
- ((lowerer.typeOf(decl.name).flags & ts.TypeFlags.Any) !== 0 ||
3664
+ const checkerAny = (lowerer.typeOf(decl.name).flags & ts.TypeFlags.Any) !== 0;
3665
+ if ((type === null || (type.kind === "dyn" && checkerAny)) && !isLet &&
3666
+ (checkerAny ||
3224
3667
  (lowerer.checkerAnyArray(decl.name) && lowerer.isArrayValueType(init.type)) ||
3225
3668
  // JS declarations carry no annotations: an unmappable inferred
3226
3669
  // type (a union with a fence-folded arm — the typeof-'bigint'
3227
3670
  // dual-mode ternary) adopts the initializer's static type, the
3228
3671
  // same rule genuine `any` consts take.
3229
- isJsSourceFile(decl.getSourceFile())) &&
3672
+ (type === null && isJsSourceFile(decl.getSourceFile()))) &&
3230
3673
  init.type.kind !== "void" && init.type.kind !== "caught" && init.type.kind !== "jsval" &&
3231
3674
  !isUnitType(init.type)) {
3232
3675
  type = init.type;
@@ -3241,29 +3684,51 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3241
3684
  }
3242
3685
  if (!type)
3243
3686
  lowerer.badType(decl.name, lowerer.typeOf(decl.name));
3687
+ let settledType = type;
3688
+ const arithmeticType = decl.initializer ? lowerer.runtimeOptionalArithmeticTypes.get(decl.initializer) : undefined;
3689
+ const isStringArithmeticUnion = (t) => {
3690
+ if (t.kind !== "union")
3691
+ return false;
3692
+ const arms = lowerer.unions.get(t.unionId)?.arms;
3693
+ return !!arms && arms.length === 2 &&
3694
+ arms.some((a) => a.kind === "f64") && arms.some((a) => a.kind === "string");
3695
+ };
3696
+ const runtimeStringArithmetic = settledType.kind === "string" &&
3697
+ ((arithmeticType && typeEquals(arithmeticType, init.type)) || isStringArithmeticUnion(init.type));
3698
+ if (runtimeStringArithmetic) {
3699
+ settledType = init.type;
3700
+ }
3701
+ const optionalSelfWriteType = isLet
3702
+ ? npmStaticOptionalSelfWriteType(lowerer, decl, settledType)
3703
+ : null;
3704
+ settledType = lowerer.runtimeOptionalBindingType(decl.name, settledType);
3705
+ if (optionalSelfWriteType !== null)
3706
+ settledType = optionalSelfWriteType;
3707
+ if (returnedEmptyArrayType !== null)
3708
+ settledType = returnedEmptyArrayType;
3244
3709
  // `const x: void = undefined` / `let y: undefined = undefined`: the
3245
3710
  // unit-only union — the initializer's unit literal wraps into its arm
3246
3711
  // like any optional completion. Non-literal void initializers (a
3247
3712
  // void CALL's result) keep their fence at the coercion below.
3248
- if (type.kind === "void" && isUnitOnlyTsType(lowerer.typeOf(decl.name))) {
3249
- type = unitOnlyUnion(lowerer.unions);
3713
+ if (settledType.kind === "void" && isUnitOnlyTsType(lowerer.typeOf(decl.name))) {
3714
+ settledType = unitOnlyUnion(lowerer.unions);
3250
3715
  }
3251
- if (type.kind === "void")
3716
+ if (settledType.kind === "void")
3252
3717
  lowerer.badType(decl.name, lowerer.typeOf(decl.name));
3253
3718
  // A PACKAGE promise stored in a local stays an island HANDLE — the
3254
3719
  // engine's promise has no static promise value to coerce into. Awaits
3255
3720
  // and .catch/.finally chains on the local bridge per use site (each
3256
3721
  // bridge is an independent observer of the same settlement).
3257
- if (init.type.kind === "jsval" && type.kind === "promise")
3258
- type = JSVAL;
3722
+ if (init.type.kind === "jsval" && settledType.kind === "promise")
3723
+ settledType = JSVAL;
3259
3724
  // An island value under an `any[]`-declared LOCAL spelling stays an
3260
3725
  // island HANDLE too (the runtime-world rule): the jsval-element-array
3261
3726
  // exit is a CALL-boundary conversion (the loadPlugins param ABI) —
3262
3727
  // converting a local would strand the engine's own prototypes
3263
3728
  // (join/map/... on a native handle-element array), where the handle
3264
3729
  // routes every use engine-side.
3265
- if (init.type.kind === "jsval" && type.kind === "array" && type.elem.kind === "jsval") {
3266
- type = JSVAL;
3730
+ if (init.type.kind === "jsval" && settledType.kind === "array" && settledType.elem.kind === "jsval") {
3731
+ settledType = JSVAL;
3267
3732
  }
3268
3733
  // A STATIC array of island HANDLES under an unannotated declared type
3269
3734
  // spelling evolved elements (`const kept = fns.filter(...)` over an
@@ -3276,8 +3741,8 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3276
3741
  // validated-boundary fence.
3277
3742
  if (!decl.type &&
3278
3743
  init.type.kind === "array" && init.type.elem.kind === "jsval" &&
3279
- type.kind === "array" && type.elem.kind !== "jsval") {
3280
- type = init.type;
3744
+ settledType.kind === "array" && settledType.elem.kind !== "jsval") {
3745
+ settledType = init.type;
3281
3746
  }
3282
3747
  // `const runner = options.runner || defaultRunner`: the checker types
3283
3748
  // the || as the union/merge of two structurally-compatible function
@@ -3287,13 +3752,13 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3287
3752
  // when the parameters agree and the declared return either contains
3288
3753
  // the init's return as a union arm or is the opaque spawnRes the
3289
3754
  // adapter converts (nothing coerces INTO either declared form).
3290
- if (type.kind === "func" && init.type.kind === "func" && !typeEquals(type, init.type)) {
3755
+ if (settledType.kind === "func" && init.type.kind === "func" && !typeEquals(settledType, init.type)) {
3291
3756
  const initFn = init.type;
3292
- const paramsAgree = type.params.length === initFn.params.length &&
3293
- type.params.every((p, i) => typeEquals(p, initFn.params[i]));
3294
- const retUnionArm = type.ret.kind === "union" && lowerer.armTag(type.ret.unionId, initFn.ret) >= 0;
3295
- if (paramsAgree && (retUnionArm || lowerer.spawnResFnAdapterPlan(type, initFn) !== null)) {
3296
- type = init.type;
3757
+ const paramsAgree = settledType.params.length === initFn.params.length &&
3758
+ settledType.params.every((p, i) => typeEquals(p, initFn.params[i]));
3759
+ const retUnionArm = settledType.ret.kind === "union" && lowerer.armTag(settledType.ret.unionId, initFn.ret) >= 0;
3760
+ if (paramsAgree && (retUnionArm || lowerer.spawnResFnAdapterPlan(settledType, initFn) !== null)) {
3761
+ settledType = init.type;
3297
3762
  }
3298
3763
  }
3299
3764
  // The downstream twin: `const result = runner(cmd, args)` where the
@@ -3302,12 +3767,12 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3302
3767
  // can narrow (spawnRes has no discriminant). The VALUE is statically
3303
3768
  // the record arm, so the local adopts it; flows into the union slot
3304
3769
  // re-wrap via the ordinary arm coercion.
3305
- if (type.kind === "union" && init.type.kind === "record") {
3306
- const arms = lowerer.unions.get(type.unionId)?.arms ?? [];
3770
+ if (settledType.kind === "union" && init.type.kind === "record") {
3771
+ const arms = lowerer.unions.get(settledType.unionId)?.arms ?? [];
3307
3772
  if (arms.length === 2 &&
3308
3773
  arms.some((a) => a.kind === "spawnRes") &&
3309
3774
  arms.some((a) => typeEquals(a, init.type))) {
3310
- type = init.type;
3775
+ settledType = init.type;
3311
3776
  }
3312
3777
  }
3313
3778
  // `const r: Repo = new MemRepo()` over an all-generic-method
@@ -3315,8 +3780,8 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3315
3780
  // (the record shape maps empty and the width copy would drop the
3316
3781
  // class the generic-method calls monomorphize against) — see
3317
3782
  // genericIfaceBindingKeepsClass.
3318
- if (type.kind === "record" && init.type.kind === "object" && genericIfaceBindingKeepsClass(lowerer, decl, type)) {
3319
- type = init.type;
3783
+ if (settledType.kind === "record" && init.type.kind === "object" && genericIfaceBindingKeepsClass(lowerer, decl, settledType)) {
3784
+ settledType = init.type;
3320
3785
  }
3321
3786
  // A TDZ box minted DURING this very initializer (a callback inside it
3322
3787
  // captured this const — predeclareForwardCapture's current-statement
@@ -3331,8 +3796,11 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3331
3796
  }
3332
3797
  // Slot coercion: `const r: A | B = bValue;` wraps implicitly; width
3333
3798
  // subtyping (`const p: {a: number} = wider;`) is rejected, not coerced.
3334
- init = lowerer.coerceInto(decl.initializer, init, type);
3335
- const local = lowerer.declareLocal(decl.name, decl.name.text, type, isLet);
3799
+ init = lowerer.coerceInto(decl.initializer, init, settledType);
3800
+ const local = lowerer.declareLocal(decl.name, decl.name.text, settledType, isLet);
3801
+ if (runtimeStringArithmetic && isStringArithmeticUnion(init.type)) {
3802
+ lowerer.runtimeOptionalArithmeticLocals.add(lowerer.runtimeOptionalRootOf(local));
3803
+ }
3336
3804
  if (runtimeOptional) {
3337
3805
  const runtimeOptionalRoot = lowerer.runtimeOptionalRootOf(local);
3338
3806
  lowerer.runtimeOptionalLocals.add(runtimeOptionalRoot);
@@ -3340,6 +3808,40 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3340
3808
  }
3341
3809
  return { kind: "varDecl", localId: local.id, init, loc: locOf(decl) };
3342
3810
  }
3811
+ function lowerVariableInitializer(lowerer, node) {
3812
+ let value = node;
3813
+ while (ts.isParenthesizedExpression(value) ||
3814
+ ts.isAsExpression(value) ||
3815
+ ts.isTypeAssertion(value) ||
3816
+ ts.isSatisfiesExpression(value)) {
3817
+ value = value.expression;
3818
+ }
3819
+ if (!ts.isObjectLiteralExpression(value))
3820
+ return lowerer.lowerExpr(node);
3821
+ const previous = new Map();
3822
+ for (const property of value.properties) {
3823
+ if (!ts.isPropertyAssignment(property) || !ts.isComputedPropertyName(property.name))
3824
+ continue;
3825
+ const keyNode = property.name.expression;
3826
+ const optional = lowerer.runtimeOptionalIdentifierValue(keyNode)?.value ??
3827
+ lowerAbsenceProbe(lowerer, keyNode);
3828
+ if (!optional || lowerer.runtimeOptionalWidening(optional.type, STRING) === null)
3829
+ continue;
3830
+ previous.set(keyNode, lowerer.chainRecvByNode.get(keyNode));
3831
+ lowerer.chainRecvByNode.set(keyNode, lowerer.ensureString(optional, keyNode));
3832
+ }
3833
+ try {
3834
+ return lowerer.lowerExpr(node);
3835
+ }
3836
+ finally {
3837
+ for (const [keyNode, prior] of previous) {
3838
+ if (prior)
3839
+ lowerer.chainRecvByNode.set(keyNode, prior);
3840
+ else
3841
+ lowerer.chainRecvByNode.delete(keyNode);
3842
+ }
3843
+ }
3844
+ }
3343
3845
  function immediatelyGuardedAbsenceProbe(lowerer, decl) {
3344
3846
  if (decl.type || !ts.isIdentifier(decl.name) || !decl.initializer)
3345
3847
  return false;
@@ -3397,9 +3899,8 @@ export function lowerSwitch(lowerer, stmt) {
3397
3899
  const labels = lowerer.takeLabels();
3398
3900
  const disc = lowerer.lowerExpr(stmt.expression);
3399
3901
  const dk = disc.type.kind;
3400
- if (dk === "dyn") {
3401
- lowerer.unsupported("SC1100", stmt.expression, "switch statements on 'unknown' values");
3402
- }
3902
+ if (dk === "dyn")
3903
+ return lowerDynSwitch(lowerer, stmt, disc);
3403
3904
  if (dk === "union")
3404
3905
  return lowerUnionSwitch(lowerer, stmt, disc);
3405
3906
  if (dk !== "f64" && dk !== "string" && dk !== "bool") {
@@ -3432,8 +3933,8 @@ export function lowerSwitch(lowerer, stmt) {
3432
3933
  * exactly `disc === test`), because the backend switch compares plain
3433
3934
  * primitives only. The desugar is JS-exact for the shape it accepts and
3434
3935
  * fences everything it cannot reproduce:
3435
- * - the discriminant rides every test, so it must be a plain
3436
- * side-effect-free read (bind it to a const first otherwise);
3936
+ * - the discriminant evaluates exactly once; effectful values stabilize
3937
+ * in a hidden local that every generated equality test reuses;
3437
3938
  * - tests evaluate lazily in source order (the chain's short-circuit IS
3438
3939
  * the switch's test order — grouped empty cases `case a: case b:` OR
3439
3940
  * their tests, still in order);
@@ -3448,12 +3949,51 @@ export function lowerSwitch(lowerer, stmt) {
3448
3949
  * is last).
3449
3950
  * Case bodies share ONE lexical scope, exactly like the real switch. */
3450
3951
  function lowerUnionSwitch(lowerer, stmt, disc) {
3451
- const loc = locOf(stmt);
3452
3952
  if (disc.type.kind !== "union")
3453
3953
  throw new InternalCompilerError("lowerer bug: non-union disc");
3454
3954
  const unionType = disc.type;
3455
- if (!pureReemittable(disc) || !lowerer.eqComparableUnion(unionType.unionId)) {
3456
- lowerer.unsupported("SC1090", stmt.expression, "switch on union-typed values that aren't plain reads (the tests re-read the discriminant — bind it to a const first, or switch on a discriminant field)");
3955
+ if (!lowerer.eqComparableUnion(unionType.unionId)) {
3956
+ lowerer.unsupported("SC1090", stmt.expression, "switch on a union whose arms do not have static equality");
3957
+ }
3958
+ return lowerBranchSwitch(lowerer, stmt, disc, (stableDisc, test, expression) => {
3959
+ const unitTest = test.kind === "unitLit"
3960
+ ? lowerer.lowerUnitComparison(stableDisc, test, false, locOf(expression))
3961
+ : null;
3962
+ return unitTest ?? {
3963
+ kind: "unionEq",
3964
+ unionId: unionType.unionId,
3965
+ negated: false,
3966
+ sameValue: false,
3967
+ left: stableDisc,
3968
+ right: lowerer.coerceInto(expression, test, unionType),
3969
+ type: BOOL,
3970
+ loc: locOf(expression),
3971
+ };
3972
+ });
3973
+ }
3974
+ /** A checked-dynamic switch whose tests have native strict-equality
3975
+ * answers. Scalar tests use dynScalarEq; null/undefined use dyn kind tests.
3976
+ * Other case values retain the existing unknown-switch fence. */
3977
+ function lowerDynSwitch(lowerer, stmt, disc) {
3978
+ return lowerBranchSwitch(lowerer, stmt, disc, (stableDisc, test, expression) => {
3979
+ if (test.kind === "unitLit") {
3980
+ return { kind: "dynTest", test: test.unit, value: stableDisc, type: BOOL, loc: locOf(expression) };
3981
+ }
3982
+ if (test.type.kind !== "string" && test.type.kind !== "f64" &&
3983
+ test.type.kind !== "bool" && test.type.kind !== "dyn") {
3984
+ lowerer.unsupported("SC1100", expression, "switch case values that cannot compare strictly with 'unknown'");
3985
+ }
3986
+ return { kind: "dynScalarEq", left: stableDisc, right: test, type: BOOL, loc: locOf(expression) };
3987
+ });
3988
+ }
3989
+ function lowerBranchSwitch(lowerer, stmt, disc, compare) {
3990
+ const loc = locOf(stmt);
3991
+ const prefix = [];
3992
+ let stableDisc = disc;
3993
+ if (!isSafeToRepeat(disc)) {
3994
+ const temp = lowerer.declareHiddenLocal("%switch", disc.type);
3995
+ prefix.push({ kind: "varDecl", localId: temp.id, init: disc, loc });
3996
+ stableDisc = { kind: "varRef", localId: temp.id, type: disc.type, loc };
3457
3997
  }
3458
3998
  const clauses = stmt.caseBlock.clauses;
3459
3999
  // An unlabeled break at a clause's END exits the switch — the chain's
@@ -3480,13 +4020,19 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3480
4020
  }
3481
4021
  }
3482
4022
  }
4023
+ const statementExits = (last) => {
4024
+ if (!last)
4025
+ return false;
4026
+ if ((ts.isBreakStatement(last) && !last.label) ||
4027
+ ts.isReturnStatement(last) ||
4028
+ ts.isThrowStatement(last) ||
4029
+ ts.isContinueStatement(last))
4030
+ return true;
4031
+ return ts.isBlock(last) && statementExits(last.statements[last.statements.length - 1]);
4032
+ };
3483
4033
  const exits = (clause) => {
3484
4034
  const last = clause.statements[clause.statements.length - 1];
3485
- return (!!last &&
3486
- ((ts.isBreakStatement(last) && !last.label) ||
3487
- ts.isReturnStatement(last) ||
3488
- ts.isThrowStatement(last) ||
3489
- ts.isContinueStatement(last)));
4035
+ return statementExits(last);
3490
4036
  };
3491
4037
  // The whole case-body sequence is ONE lexical scope, like the real
3492
4038
  // switch lowering.
@@ -3506,7 +4052,7 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3506
4052
  // final else, so matching them changes nothing when pure).
3507
4053
  if (test.kind !== "strLit" && test.kind !== "numLit" &&
3508
4054
  test.kind !== "boolLit" && test.kind !== "unitLit" &&
3509
- !pureReemittable(test)) {
4055
+ !isSafeToRepeat(test)) {
3510
4056
  lowerer.unsupported("SC1090", clause.expression, "effectful case tests in a union-typed switch (bind the test value to a const first)");
3511
4057
  }
3512
4058
  // A unit-literal test takes the unit-comparison lowering: a tag
@@ -3514,19 +4060,7 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3514
4060
  // lacks it (`case null:` on a `number | undefined` — legal TS,
3515
4061
  // never matches; coercing the literal into the union would hit
3516
4062
  // the stranded-arm trap and throw where JS just skips the case).
3517
- const unitTest = test.kind === "unitLit"
3518
- ? lowerer.lowerUnitComparison(disc, test, false, locOf(clause.expression))
3519
- : null;
3520
- pendingTests.push(unitTest ?? {
3521
- kind: "unionEq",
3522
- unionId: unionType.unionId,
3523
- negated: false,
3524
- sameValue: false,
3525
- left: disc,
3526
- right: lowerer.coerceInto(clause.expression, test, unionType),
3527
- type: BOOL,
3528
- loc: locOf(clause.expression),
3529
- });
4063
+ pendingTests.push(compare(stableDisc, test, clause.expression));
3530
4064
  }
3531
4065
  const isDefault = ts.isDefaultClause(clause);
3532
4066
  if (clause.statements.length === 0 && !isDefault && i < clauses.length - 1) {
@@ -3563,13 +4097,13 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3563
4097
  }
3564
4098
  chain = [{ kind: "if", cond, then: g.body, else_: chain.length > 0 ? chain : null, loc }];
3565
4099
  }
3566
- return { kind: "block", body: chain, loc };
4100
+ return { kind: "block", body: [...prefix, ...chain], loc };
3567
4101
  }
3568
4102
  finally {
3569
4103
  lowerer.scopes.pop();
3570
4104
  }
3571
4105
  }
3572
- /** try/catch/finally. Supported subset:
4106
+ /** try/catch/finally:
3573
4107
  * - `catch { }` (bindingless) discards the thrown value on entry.
3574
4108
  * - `catch (e)` binds it as a CAUGHT local — a snapshot of the exception
3575
4109
  * cell, deliberately narrower than dyn: the supported uses are the
@@ -3580,23 +4114,14 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3580
4114
  * assignment are fenced (SC1063 and friends); destructuring patterns
3581
4115
  * are SC1062. The `: any` / `: unknown` annotations tsc admits both
3582
4116
  * lower the same way (tsc narrows either through the tests).
3583
- * - `finally` runs on normal completion, on exception paths, and on the
3584
- * way out of a `return` in the try/catch body (the backend's
3585
- * pending-return path — inner-to-outer through nested finallys, the
3586
- * value snapshotted before the finally runs, Node-exact).
3587
- * break/continue crossing a try-with-finally stay rejected, as does
3588
- * any jump out of the finally body itself (it would replace a pending
3589
- * completion — rejectJumpCrossingFinally). Plain try/catch places no
3590
- * restriction on jumps.
4117
+ * - `finally` runs on normal completion, exception paths, and every
4118
+ * abrupt completion that crosses it. Return values are snapshotted
4119
+ * before cleanup; an abrupt completion inside the finally replaces the
4120
+ * pending one, matching JavaScript completion-record semantics.
3591
4121
  * Each block is its own lexical scope (the binding lives in a scope
3592
4122
  * WRAPPING the catch block, like the spec's catch environment). */
3593
4123
  export function lowerTry(lowerer, stmt) {
3594
- const hasFinally = stmt.finallyBlock !== undefined;
3595
- // try/catch bodies are jump-fenced only when a finally guards them.
3596
- const lowerGuarded = (block) => hasFinally
3597
- ? lowerer.inCtl("tryFinally", () => lowerer.lowerScopedBlock(block))
3598
- : lowerer.lowerScopedBlock(block);
3599
- const tryBody = lowerGuarded(stmt.tryBlock);
4124
+ const tryBody = lowerer.lowerScopedBlock(stmt.tryBlock);
3600
4125
  let catchBody = null;
3601
4126
  let catchLocalId = null;
3602
4127
  if (stmt.catchClause) {
@@ -3609,19 +4134,17 @@ export function lowerTry(lowerer, stmt) {
3609
4134
  try {
3610
4135
  const local = lowerer.declareLocal(vd.name, vd.name.text, CAUGHT, false);
3611
4136
  catchLocalId = local.id;
3612
- catchBody = lowerGuarded(stmt.catchClause.block);
4137
+ catchBody = lowerer.lowerScopedBlock(stmt.catchClause.block);
3613
4138
  }
3614
4139
  finally {
3615
4140
  lowerer.scopes.pop();
3616
4141
  }
3617
4142
  }
3618
4143
  else {
3619
- catchBody = lowerGuarded(stmt.catchClause.block);
4144
+ catchBody = lowerer.lowerScopedBlock(stmt.catchClause.block);
3620
4145
  }
3621
4146
  }
3622
- const finallyBody = stmt.finallyBlock
3623
- ? lowerer.inCtl("finallyBlock", () => lowerer.lowerScopedBlock(stmt.finallyBlock))
3624
- : null;
4147
+ const finallyBody = stmt.finallyBlock ? lowerer.lowerScopedBlock(stmt.finallyBlock) : null;
3625
4148
  return {
3626
4149
  kind: "tryCatch",
3627
4150
  tryBody,
@@ -3645,12 +4168,16 @@ function lowerDeleteStatement(lowerer, expr) {
3645
4168
  if (!ts.isElementAccessExpression(target) && !ts.isPropertyAccessExpression(target)) {
3646
4169
  lowerer.unsupported("SC1090", expr, "'delete' of non-property expressions");
3647
4170
  }
4171
+ fenceNodeModuleMutation(lowerer, target, "delete");
3648
4172
  const lowerKey = () => {
3649
4173
  if (ts.isPropertyAccessExpression(target)) {
3650
4174
  return { kind: "strLit", value: target.name.text, type: STRING, loc: locOf(target.name) };
3651
4175
  }
3652
4176
  const keyNode = target.argumentExpression;
3653
- const key = lowerer.lowerExpr(keyNode);
4177
+ let key = lowerer.lowerExpr(keyNode);
4178
+ if (lowerer.runtimeOptionalWidening(key.type, STRING) !== null) {
4179
+ key = lowerer.ensureString(key, keyNode);
4180
+ }
3654
4181
  if (key.type.kind !== "string") {
3655
4182
  lowerer.unsupported("SC1090", keyNode, `'delete' with '${lowerer.fmt(key.type)}' keys (index-signature and env keys are strings)`);
3656
4183
  }
@@ -4135,6 +4662,22 @@ export function lowerExprStatement(lowerer, expr) {
4135
4662
  if (ts.isElementAccessExpression(expr.left))
4136
4663
  return lowerer.lowerElementWrite(expr);
4137
4664
  if (ts.isPropertyAccessExpression(expr.left)) {
4665
+ fenceNodeModuleMutation(lowerer, expr.left, "assignment");
4666
+ // Native arrays have a writable length property. The runtime grows
4667
+ // by creating holes and truncates with RC-aware removal; keep this
4668
+ // as a statement operation so invalid lengths can use the normal
4669
+ // pending-exception path.
4670
+ if (!expr.left.questionDotToken && expr.left.name.text === "length") {
4671
+ const arrayType = lowerer.mapTypeOf(lowerer.typeOf(expr.left.expression));
4672
+ if (arrayType?.kind === "array") {
4673
+ const arr = lowerer.lowerExpr(expr.left.expression);
4674
+ if (arr.type.kind !== "array") {
4675
+ lowerer.unsupported("SC1090", expr.left.expression, "writing the length of an array-typed value that remains an island handle");
4676
+ }
4677
+ const length = lowerer.lowerExprExpecting(expr.right, F64);
4678
+ return { kind: "arraySetLength", arr, length, loc: locOf(expr) };
4679
+ }
4680
+ }
4138
4681
  // `process.env.NAME = v` — setenv(3): later env reads and spawned
4139
4682
  // children observe the write, exactly Node. Values are strings
4140
4683
  // (Node stringifies everything; a non-string RHS fences instead
@@ -4272,10 +4815,10 @@ export function lowerExprStatement(lowerer, expr) {
4272
4815
  // chain): the dyn keyed write — the value converts INTO dyn
4273
4816
  // (closures box), the runtime sets the member on OBJ receivers
4274
4817
  // and throws Node's TypeErrors on the rest. The receiver is
4275
- // PROBED (probeLower) and claimed exactly when it lowers to
4818
+ // PROBED (tryLowerExpression) and claimed exactly when it lowers to
4276
4819
  // dyn; typed receivers keep their own lowerings and fences.
4277
4820
  if (!expr.left.questionDotToken) {
4278
- const recv = probeLower(lowerer, expr.left.expression);
4821
+ const recv = tryLowerExpression(lowerer, expr.left.expression);
4279
4822
  if (recv && recv.type.kind === "dyn") {
4280
4823
  const loc = locOf(expr);
4281
4824
  const key = { kind: "strLit", value: expr.left.name.text, type: STRING, loc: locOf(expr.left.name) };
@@ -4571,6 +5114,7 @@ function lowerCompoundToTarget(lowerer, expr, compound, target) {
4571
5114
  const loc = locOf(expr);
4572
5115
  const read = { kind: "varRef", localId: target.id, type: target.type, loc: locOf(expr.left) };
4573
5116
  const rhs = lowerer.lowerExpr(expr.right);
5117
+ const numericRhs = lowerOptionalNumber(lowerer, rhs, loc);
4574
5118
  let value;
4575
5119
  if (target.type.kind === "jsval" || rhs.type.kind === "jsval") {
4576
5120
  const JS_COMPOUND = { "+": "add", "-": "sub", "*": "mul", "/": "div", "%": "mod", "**": "pow" };
@@ -4587,8 +5131,8 @@ function lowerCompoundToTarget(lowerer, expr, compound, target) {
4587
5131
  else if (compound === "+" && target.type.kind === "string") {
4588
5132
  value = { kind: "strConcat", left: read, right: lowerer.ensureString(rhs, expr.right), type: STRING, loc };
4589
5133
  }
4590
- else if (target.type.kind === "f64" && rhs.type.kind === "f64") {
4591
- value = { kind: "bin", op: compound, left: read, right: rhs, type: F64, loc };
5134
+ else if (target.type.kind === "f64" && numericRhs.type.kind === "f64") {
5135
+ value = { kind: "bin", op: compound, left: read, right: numericRhs, type: F64, loc };
4592
5136
  }
4593
5137
  else if ((target.type.kind === "dyn" || rhs.type.kind === "dyn") &&
4594
5138
  (target.type.kind === "dyn" || target.type.kind === "f64") &&
@@ -4843,6 +5387,16 @@ export function lowerDestructuringAssignParts(lowerer, target, rhs, loc) {
4843
5387
  * source-spelling TypeError machinery cannot apply), `blame` the node
4844
5388
  * fences point at. */
4845
5389
  function destructuringAssignInto(lowerer, target, init, rhs, blame, loc) {
5390
+ if (init.type.kind === "union") {
5391
+ const source = lowerer.declareHiddenLocal("%destrSource", init.type);
5392
+ const out = [{ kind: "varDecl", localId: source.id, init, loc }];
5393
+ const value = varRef(source.id, init.type, loc);
5394
+ const present = patternSourceValue(lowerer, value, ts.isArrayLiteralExpression(target), out, loc);
5395
+ if (!typeEquals(present.type, init.type)) {
5396
+ const sub = destructuringAssignInto(lowerer, target, present, rhs, blame, loc);
5397
+ return { stmts: [...out, ...sub.stmts], value };
5398
+ }
5399
+ }
4846
5400
  // IR-string sources in JS files split two ways the carrier type
4847
5401
  // cannot: a STDLIB GLOBAL (`({ subtle } = globalThis.crypto)`) — the
4848
5402
  // declaration path binds member identity tokens into FRESH bindings,
@@ -5316,14 +5870,10 @@ function lowerAssignTargetInto(lowerer, out, targetNode, blame, dflt, valueOf) {
5316
5870
  }
5317
5871
  const idx = lowerer.declareHiddenLocal("%dtIdx", F64);
5318
5872
  out.push({ kind: "varDecl", localId: idx.id, init: index, loc });
5319
- const value = lowerer.coerceInto(blame, valueOf(recvT.elem), recvT.elem);
5320
- out.push({
5321
- kind: "arraySet",
5322
- arr: { kind: "varRef", localId: recv.id, type: arr.type, loc },
5323
- index: { kind: "varRef", localId: idx.id, type: F64, loc },
5324
- value,
5325
- loc,
5326
- });
5873
+ const value = valueOf(recvT.elem);
5874
+ const stored = lowerer.declareHiddenLocal("%dtValue", value.type);
5875
+ out.push({ kind: "varDecl", localId: stored.id, init: value, loc });
5876
+ out.push(arrayValueStore(lowerer, varRef(recv.id, arr.type, loc), varRef(idx.id, F64, loc), varRef(stored.id, value.type, loc), recvT.elem, loc));
5327
5877
  return;
5328
5878
  }
5329
5879
  }
@@ -5499,11 +6049,9 @@ function lowerArrayAssignInto(lowerer, out, target, tmpRef, blame, loc) {
5499
6049
  loc: locOf(at),
5500
6050
  };
5501
6051
  }
5502
- // Plain arrays read by index — a pattern wider than the runtime
5503
- // array traps (the arrayGet discipline, divergence 4's policy; JS
5504
- // would answer undefined).
6052
+ // Assignment patterns consume the same values as binding patterns.
5505
6053
  const elemType = srcType.elem;
5506
- return { kind: "arrayGet", arr: tmpRef(), index: { kind: "numLit", value: i, type: F64, loc: locOf(at) }, type: elemType, loc: locOf(at) };
6054
+ return arrayValueRead(lowerer, tmpRef(), numLit(i, locOf(at)), elemType, locOf(at));
5507
6055
  };
5508
6056
  elements.forEach((el, i) => {
5509
6057
  if (ts.isOmittedExpression(el))
@@ -5556,7 +6104,7 @@ function lowerArrayAssignInto(lowerer, out, target, tmpRef, blame, loc) {
5556
6104
  }
5557
6105
  return isTuple
5558
6106
  ? tupleTailValue(lowerer, el, tmpRef, srcType, shape, i, targetT)
5559
- : { kind: "arrIntrinsic", method: "slice", receiver: tmpRef(), args: [{ kind: "numLit", value: i, type: F64, loc: locOf(el) }], type: srcType, loc: locOf(el) };
6107
+ : arrayRestValue(tmpRef(), i, srcType, locOf(el));
5560
6108
  }
5561
6109
  if (isTuple && defaultNode !== null && targetT !== null && !shape.fields.some((f) => f.name === String(i))) {
5562
6110
  // Past the tuple's end the read is always undefined — the default
@@ -5626,29 +6174,27 @@ export function lowerForOf(lowerer, stmt) {
5626
6174
  // desugars drop them — a labeled jump naming those loops fences at the
5627
6175
  // jump site (no label point exists in their desugared shape).
5628
6176
  const labels = lowerer.takeLabels();
5629
- // `for await` is ASYNC ITERATION, not the shipped async/await. ONE
5630
- // async iterable has a lowering: process.stdin (the piped-input
5631
- // pattern real CLIs use) — see lowerForAwaitStdin. Everything else is
5632
- // named specifically (the bare SC1070 text would claim async/await
5633
- // itself is missing).
6177
+ // `for await` is ASYNC ITERATION, not the shipped async/await. Typed
6178
+ // async generators use the generic request protocol; process.stdin
6179
+ // and readable streams retain their dedicated next-chunk paths.
5634
6180
  if (stmt.awaitModifier) {
5635
- if (ts.isPropertyAccessExpression(stmt.expression) &&
5636
- lowerer.stdlibGlobalMember(stmt.expression, "process") === "stdin") {
5637
- return lowerForAwaitStdin(lowerer, stmt);
5638
- }
5639
- // Readable streams (the readableAsyncIterator surface): the mapped
5640
- // receiver class roots at a readable-sided stream class.
5641
6181
  {
5642
- const recvT = lowerer.mapTypeOf(lowerer.typeOf(stmt.expression));
5643
- if (recvT?.kind === "object") {
5644
- const info = lowerer.classes.get(recvT.className);
5645
- const sides = streamSidesOf(lowerer, info);
5646
- if (sides === "r" || sides === "rw") {
5647
- return lowerForAwaitReadable(lowerer, stmt, recvT);
6182
+ const genT = lowerer.mapTypeOf(lowerer.typeOf(stmt.expression));
6183
+ if (genT?.kind === "generator" && genT.async) {
6184
+ const iterable = lowerer.lowerExpr(stmt.expression);
6185
+ if (iterable.type.kind === "generator" && iterable.type.async) {
6186
+ return lowerForAwaitGenerator(lowerer, stmt, iterable, labels);
5648
6187
  }
5649
6188
  }
5650
6189
  }
5651
- lowerer.unsupported("SC1070", stmt, "'for await' (async iteration over anything but process.stdin and readable streams)");
6190
+ if (ts.isPropertyAccessExpression(stmt.expression) &&
6191
+ lowerer.stdlibGlobalMember(stmt.expression, "process") === "stdin") {
6192
+ return lowerForAwaitStdin(lowerer, stmt);
6193
+ }
6194
+ const builtin = lowerForAwaitBuiltin(lowerer, stmt);
6195
+ if (builtin)
6196
+ return builtin;
6197
+ lowerer.unsupported("SC1070", stmt, "'for await' over this value (supported: typed async generators, represented class async iterators, process.stdin, and Node/Web readable streams)");
5652
6198
  }
5653
6199
  lowerer.fenceStaticHeadersIteration(stmt.expression);
5654
6200
  // A stored numeric value iterator declared in this function keeps its
@@ -5777,6 +6323,19 @@ export function lowerForOf(lowerer, stmt) {
5777
6323
  }
5778
6324
  iterSrc ??= stmt.expression;
5779
6325
  let iterable = lowerer.lowerExpr(iterSrc);
6326
+ // A nested loop receiver can be an OOB-safe array read from the outer
6327
+ // loop (`for (const row of grid) for (const cell of row)`). The checker
6328
+ // sees the inner value as an array, while its runtime slot is array |
6329
+ // undefined; validate the receiver before selecting the array iterator.
6330
+ const checkerIterable = lowerer.mapTypeOf(lowerer.typeOf(iterSrc));
6331
+ if (iterable.type.kind === "union" && lowerer.armTag(iterable.type.unionId, UNDEFINED_T) >= 0 && checkerIterable?.kind === "array") {
6332
+ const present = lowerer.stripUndefinedArm(iterable.type);
6333
+ const helper = present.kind === "array"
6334
+ ? lowerer.narrowedArmHelper(iterable.type.unionId, present, locOf(iterSrc))
6335
+ : null;
6336
+ if (helper)
6337
+ iterable = { kind: "call", callee: helper, args: [iterable], type: present, loc: locOf(iterSrc) };
6338
+ }
5780
6339
  if (iterable.type.kind === "bytes") {
5781
6340
  return lowerForOfBytes(lowerer, stmt, iterable, labels);
5782
6341
  }
@@ -5854,7 +6413,7 @@ export function lowerForOf(lowerer, stmt) {
5854
6413
  elemT = { kind: "union", unionId: lowerer.unions.intern(arms) };
5855
6414
  }
5856
6415
  }
5857
- if (elemT !== null && pureReemittable(iterable)) {
6416
+ if (elemT !== null && isSafeToRepeat(iterable)) {
5858
6417
  const loc = locOf(stmt.expression);
5859
6418
  const shapeId = iterable.type.shapeId;
5860
6419
  const snapshotElem = elemT;
@@ -5902,6 +6461,29 @@ export function lowerForOf(lowerer, stmt) {
5902
6461
  lowerer.badType(stmt.expression, lowerer.typeOf(stmt.expression));
5903
6462
  }
5904
6463
  }
6464
+ const elemValueT = arrayValueType(lowerer, iterable.type.elem);
6465
+ // Native arrays retain their payload ABI; the loop binds the value yielded
6466
+ // by Get, which is undefined for both holes and present undefined slots.
6467
+ const sourceT = iterable.type;
6468
+ const source = lowerer.declareHiddenLocal("%arrayIterator", sourceT);
6469
+ const cursor = lowerer.declareHiddenLocal("%arrayCursor", F64);
6470
+ cursor.mutable = true;
6471
+ const forValues = (localId, body) => ({
6472
+ kind: "block",
6473
+ body: [
6474
+ { kind: "varDecl", localId: source.id, init: iterable, loc: locOf(stmt) },
6475
+ {
6476
+ kind: "for",
6477
+ init: { kind: "varDecl", localId: cursor.id, init: numLit(0, locOf(stmt)), loc: locOf(stmt) },
6478
+ cond: { kind: "bin", op: "<", left: varRef(cursor.id, F64, locOf(stmt)), right: { kind: "arrIntrinsic", method: "length", receiver: varRef(source.id, sourceT, locOf(stmt)), args: [], type: F64, loc: locOf(stmt) }, type: BOOL, loc: locOf(stmt) },
6479
+ update: { kind: "assign", localId: cursor.id, value: { kind: "bin", op: "+", left: varRef(cursor.id, F64, locOf(stmt)), right: numLit(1, locOf(stmt)), type: F64, loc: locOf(stmt) }, loc: locOf(stmt) },
6480
+ body: [{ kind: "varDecl", localId, init: arrayValueRead(lowerer, varRef(source.id, sourceT, locOf(stmt)), varRef(cursor.id, F64, locOf(stmt)), sourceT.elem, locOf(stmt)), loc: locOf(stmt) }, ...body],
6481
+ ...(labels && { labels }),
6482
+ loc: locOf(stmt),
6483
+ },
6484
+ ],
6485
+ loc: locOf(stmt),
6486
+ });
5905
6487
  if (!ts.isVariableDeclarationList(stmt.initializer)) {
5906
6488
  // `for (x of xs)` over a PRE-DECLARED writable binding: JS assigns
5907
6489
  // the existing binding once per pass — one shared binding across
@@ -5914,18 +6496,18 @@ export function lowerForOf(lowerer, stmt) {
5914
6496
  while (ts.isParenthesizedExpression(target))
5915
6497
  target = target.expression;
5916
6498
  if (ts.isArrayLiteralExpression(target) || ts.isObjectLiteralExpression(target)) {
5917
- const elemType = iterable.type.elem;
6499
+ const elemType = elemValueT;
5918
6500
  const loc = locOf(target);
5919
6501
  const tmp = lowerer.declareHiddenLocal("%vof", elemType);
5920
6502
  const elemRef = { kind: "varRef", localId: tmp.id, type: elemType, loc };
5921
6503
  const assigns = lowerDestructuringAssign(lowerer, target, elemRef, target, loc);
5922
6504
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
5923
- return { kind: "forOf", localId: tmp.id, iterable, body: [assigns, ...body], ...(labels && { labels }), loc: locOf(stmt) };
6505
+ return forValues(tmp.id, [assigns, ...body]);
5924
6506
  }
5925
6507
  if (ts.isIdentifier(target)) {
5926
6508
  const writable = lowerer.resolveWritable(target);
5927
6509
  if (writable) {
5928
- const elemType = iterable.type.elem;
6510
+ const elemType = elemValueT;
5929
6511
  const loc = locOf(target);
5930
6512
  const tmp = lowerer.declareHiddenLocal("%vof", elemType);
5931
6513
  const write = {
@@ -5935,15 +6517,13 @@ export function lowerForOf(lowerer, stmt) {
5935
6517
  loc,
5936
6518
  };
5937
6519
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
5938
- return { kind: "forOf", localId: tmp.id, iterable, body: [write, ...body], ...(labels && { labels }), loc: locOf(stmt) };
6520
+ return forValues(tmp.id, [write, ...body]);
5939
6521
  }
5940
6522
  }
5941
6523
  lowerer.unsupported("SC1090", stmt.initializer, "for-of over a pre-declared variable (declare the loop variable in the loop: for (const x of ...))");
5942
6524
  }
5943
6525
  const list = stmt.initializer;
5944
- if ((list.flags & ts.NodeFlags.Using) !== 0) {
5945
- lowerer.unsupported("SC1090", list, "'using' declarations (dispose-at-scope-exit semantics)");
5946
- }
6526
+ const resourceLoop = (list.flags & ts.NodeFlags.Using) !== 0;
5947
6527
  const isLet = (list.flags & ts.NodeFlags.Let) !== 0 || (list.flags & ts.NodeFlags.BlockScoped) === 0;
5948
6528
  const decl = list.declarations[0]; // the grammar allows exactly one
5949
6529
  // `for (const [k, v] of pairs)` — a destructuring loop variable: the
@@ -5955,13 +6535,13 @@ export function lowerForOf(lowerer, stmt) {
5955
6535
  if (ts.isArrayBindingPattern(decl.name) || ts.isObjectBindingPattern(decl.name)) {
5956
6536
  lowerer.scopes.push(new Map());
5957
6537
  try {
5958
- const elemType = iterable.type.elem;
6538
+ const elemType = elemValueT;
5959
6539
  const loc = locOf(decl.name);
5960
6540
  const tmp = lowerer.declareHiddenLocal("%destr", elemType);
5961
6541
  const binds = [];
5962
6542
  lowerer.lowerBindingPattern(decl.name, () => ({ kind: "varRef", localId: tmp.id, type: elemType, loc }), elemType, isLet, binds);
5963
6543
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
5964
- return { kind: "forOf", localId: tmp.id, iterable, body: [...binds, ...body], ...(labels && { labels }), loc: locOf(stmt) };
6544
+ return forValues(tmp.id, [...binds, ...body]);
5965
6545
  }
5966
6546
  finally {
5967
6547
  lowerer.scopes.pop();
@@ -5977,7 +6557,7 @@ export function lowerForOf(lowerer, stmt) {
5977
6557
  // after the loop (both Node-exact for var).
5978
6558
  const varTarget = forOfVarTarget(lowerer, decl);
5979
6559
  if (varTarget) {
5980
- const elemType = iterable.type.elem;
6560
+ const elemType = elemValueT;
5981
6561
  const loc = locOf(decl.name);
5982
6562
  const tmp = lowerer.declareHiddenLocal("%vof", elemType);
5983
6563
  const write = {
@@ -5987,11 +6567,28 @@ export function lowerForOf(lowerer, stmt) {
5987
6567
  loc,
5988
6568
  };
5989
6569
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
5990
- return { kind: "forOf", localId: tmp.id, iterable, body: [write, ...body], ...(labels && { labels }), loc: locOf(stmt) };
6570
+ return forValues(tmp.id, [write, ...body]);
6571
+ }
6572
+ const local = lowerer.declareLocal(decl.name, decl.name.text, elemValueT, isLet);
6573
+ if (!typeEquals(elemValueT, sourceT.elem)) {
6574
+ const root = lowerer.runtimeOptionalRootOf(local);
6575
+ lowerer.runtimeOptionalLocals.add(root);
6576
+ lowerer.runtimeOptionalStorageLocals.add(root);
5991
6577
  }
5992
- const local = lowerer.declareLocal(decl.name, decl.name.text, iterable.type.elem, isLet);
5993
6578
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
5994
- return { kind: "forOf", localId: local.id, iterable, body, ...(labels && { labels }), loc: locOf(stmt) };
6579
+ if (!resourceLoop)
6580
+ return forValues(local.id, body);
6581
+ const resource = varRef(local.id, local.type, locOf(decl.name));
6582
+ const cleanup = cleanupForValue(lowerer, decl.name, resource, isAwaitUsing(list));
6583
+ return forValues(local.id, [{
6584
+ kind: "tryCatch",
6585
+ tryBody: body,
6586
+ catchBody: null,
6587
+ catchLocalId: null,
6588
+ finallyBody: cleanup,
6589
+ suppressFinallyErrors: true,
6590
+ loc: locOf(decl),
6591
+ }]);
5995
6592
  }
5996
6593
  finally {
5997
6594
  lowerer.scopes.pop();
@@ -6087,8 +6684,9 @@ function lowerForOfStoredNumericIterator(lowerer, stmt, state, labels) {
6087
6684
  type: F64,
6088
6685
  loc,
6089
6686
  };
6687
+ const valueT = sourceT.kind === "array" ? arrayValueType(lowerer, F64) : F64;
6090
6688
  const sourceValue = () => sourceT.kind === "array"
6091
- ? { kind: "arrayGet", arr: sourceRef(), index: indexRef(), type: F64, loc }
6689
+ ? arrayValueRead(lowerer, sourceRef(), indexRef(), F64, loc)
6092
6690
  : {
6093
6691
  kind: "bytesIntrinsic",
6094
6692
  method: "get",
@@ -6102,9 +6700,14 @@ function lowerForOfStoredNumericIterator(lowerer, stmt, state, labels) {
6102
6700
  try {
6103
6701
  const varTarget = exprTarget ?? (decl ? forOfVarTarget(lowerer, decl) : null);
6104
6702
  const value = varTarget
6105
- ? lowerer.declareHiddenLocal("%numiterValue", F64)
6106
- : lowerer.declareLocal(decl.name, decl.name.text, F64, isLet);
6107
- const valueRef = varRef(value.id, F64, loc);
6703
+ ? lowerer.declareHiddenLocal("%numiterValue", valueT)
6704
+ : lowerer.declareLocal(decl.name, decl.name.text, valueT, isLet);
6705
+ if (!varTarget && valueT.kind === "union") {
6706
+ const root = lowerer.runtimeOptionalRootOf(value);
6707
+ lowerer.runtimeOptionalLocals.add(root);
6708
+ lowerer.runtimeOptionalStorageLocals.add(root);
6709
+ }
6710
+ const valueRef = varRef(value.id, valueT, loc);
6108
6711
  const blame = decl?.name ?? exprTargetNode ?? stmt.initializer;
6109
6712
  const head = [
6110
6713
  {
@@ -7141,112 +7744,6 @@ function lowerForAwaitStdin(lowerer, stmt) {
7141
7744
  lowerer.scopes.pop();
7142
7745
  }
7143
7746
  }
7144
- /** `for await (const chunk of readable)` — the stream async iterator
7145
- * (the stdin desugar's sibling): every pass awaits the runtime's
7146
- * next-chunk promise — buffered content, or a REJECTION carrying the
7147
- * stream's error (the await rethrows it, Node's iterator contract) —
7148
- * and exits on the EOF sentinel. Chunks are Buffers in typed code
7149
- * (encoded streams fence at the runtime entry); in the JS lane the
7150
- * chunk is checked-dynamic and boxes by runtime tag (dyn strings once
7151
- * an encoding applies, dyn undefined as the sentinel — chunks are
7152
- * never undefined). Early exit leaves the stream alive (Node's
7153
- * iterator return() would destroy it — a documented divergence). */
7154
- function lowerForAwaitReadable(lowerer, stmt, recvT) {
7155
- if (!lowerer.ctx.isAsync) {
7156
- lowerer.unsupported("SC1090", stmt, "top-level 'for await' (await outside async functions)");
7157
- }
7158
- if (!ts.isVariableDeclarationList(stmt.initializer)) {
7159
- lowerer.unsupported("SC1090", stmt.initializer, "for-await over a pre-declared variable (declare the loop variable in the loop: for await (const chunk of ...))");
7160
- }
7161
- const list = stmt.initializer;
7162
- const isConst = (list.flags & ts.NodeFlags.Const) !== 0;
7163
- const isLet = (list.flags & ts.NodeFlags.Let) !== 0;
7164
- // `for await (var chunk of ...)`: the chunk binding is per-await
7165
- // machinery (a fresh value each pass, fiber-parked in between) —
7166
- // threading it through a shared hoisted slot has no user yet.
7167
- if (!isConst && !isLet)
7168
- lowerer.unsupported("SC1030", list, "'var' loop bindings in 'for await' (use const)");
7169
- const decl = list.declarations[0];
7170
- if (!ts.isIdentifier(decl.name))
7171
- lowerer.unsupported("SC1031", decl.name);
7172
- const loc = locOf(stmt);
7173
- // The chunk is CHECKED-DYNAMIC in both lanes (the shim types the
7174
- // iterator's element as any): the runtime boxes by tag — Buffers
7175
- // normally, strings once an encoding applies — and the dyn surface
7176
- // carries the common consumptions (.length, [i], toString, +=,
7177
- // typeof). A statically-typed chunk would have to pick one side of
7178
- // the encoding question at compile time.
7179
- const dynLane = true;
7180
- const chunkT = DYN;
7181
- const promiseT = { kind: "promise", inner: chunkT };
7182
- lowerer.scopes.push(new Map());
7183
- try {
7184
- // The receiver evaluates ONCE, before the loop.
7185
- const recvLocal = lowerer.declareHiddenLocal("%faStream", recvT);
7186
- const recvDecl = {
7187
- kind: "varDecl",
7188
- localId: recvLocal.id,
7189
- init: lowerer.lowerExpr(stmt.expression),
7190
- loc,
7191
- };
7192
- const p = lowerer.declareHiddenLocal("%streamNext", promiseT);
7193
- const chunk = lowerer.declareLocal(decl.name, decl.name.text, chunkT, isLet);
7194
- const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement));
7195
- const chunkRef = { kind: "varRef", localId: chunk.id, type: chunkT, loc };
7196
- const eofCond = dynLane
7197
- ? { kind: "dynTest", test: "undefined", value: chunkRef, type: BOOL, loc }
7198
- : {
7199
- kind: "bin",
7200
- op: "===",
7201
- left: { kind: "bytesIntrinsic", method: "length", receiver: chunkRef, args: [], type: F64, loc },
7202
- right: { kind: "numLit", value: 0, type: F64, loc },
7203
- type: BOOL,
7204
- loc,
7205
- };
7206
- const head = [
7207
- {
7208
- kind: "varDecl",
7209
- localId: p.id,
7210
- init: {
7211
- kind: "libCall",
7212
- fn: dynLane ? "readable.nextChunkDyn" : "readable.nextChunk",
7213
- args: [{ kind: "varRef", localId: recvLocal.id, type: recvT, loc }],
7214
- type: promiseT,
7215
- loc,
7216
- },
7217
- loc,
7218
- },
7219
- {
7220
- kind: "varDecl",
7221
- localId: chunk.id,
7222
- init: {
7223
- kind: "awaitExpr",
7224
- value: { kind: "varRef", localId: p.id, type: promiseT, loc },
7225
- type: chunkT,
7226
- loc,
7227
- },
7228
- loc,
7229
- },
7230
- { kind: "if", cond: eofCond, then: [{ kind: "break", loc }], else_: null, loc },
7231
- ];
7232
- return {
7233
- kind: "block",
7234
- body: [
7235
- recvDecl,
7236
- {
7237
- kind: "while",
7238
- cond: { kind: "boolLit", value: true, type: BOOL, loc },
7239
- body: [...head, ...body],
7240
- loc,
7241
- },
7242
- ],
7243
- loc,
7244
- };
7245
- }
7246
- finally {
7247
- lowerer.scopes.pop();
7248
- }
7249
- }
7250
7747
  export function lowerForStatement(lowerer, stmt) {
7251
7748
  const labels = lowerer.takeLabels();
7252
7749
  lowerer.scopes.push(new Map());